#region License Information (GPL v3) /* ShareX - A program that allows you to take screenshots and share any file type Copyright (c) 2007-2024 ShareX Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Optionally you can also view the license at . */ #endregion License Information (GPL v3) using ShareX.HelpersLib; using ShareX.ImageEffectsLib; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Threading; using System.Windows.Forms; namespace ShareX.ScreenCaptureLib { internal partial class ShapeManager : IDisposable { public List Shapes { get; private set; } = new List(); private BaseShape currentShape; public BaseShape CurrentShape { get { return currentShape; } private set { currentShape = value; if (currentShape != null) { currentShape.OnConfigSave(); } OnCurrentShapeChanged(currentShape); } } private ShapeType currentTool; public ShapeType CurrentTool { get { return currentTool; } set { if (currentTool == value) return; currentTool = value; if (Form.IsAnnotationMode) { if (IsCurrentShapeTypeRegion) { Options.LastRegionTool = CurrentTool; } else if (Form.IsEditorMode) { Options.LastEditorTool = CurrentTool; } else { Options.LastAnnotationTool = CurrentTool; } ClearTools(); } if (CurrentShape != null) { // do not keep selection if select tool does not handle it if (currentTool == ShapeType.ToolSelect) { if (!CurrentShape.IsHandledBySelectTool) { DeselectCurrentShape(); } } // do not keep selection if we switch away from a tool and the selected shape does not match the new type else if (CurrentShape.ShapeType != currentTool) { DeselectCurrentShape(); } } OnCurrentShapeTypeChanged(currentTool); } } public ShapeType CurrentShapeTool { get { ShapeType tool = CurrentTool; if (tool == ShapeType.ToolSelect) { BaseShape shape = CurrentShape; if (shape != null) { tool = shape.ShapeType; } } return tool; } } public RectangleF CurrentRectangle { get { if (CurrentShape != null) { return CurrentShape.Rectangle; } return RectangleF.Empty; } } public PointF CurrentDPI = new PointF(96f, 96f); public bool IsCurrentShapeValid => CurrentShape != null && CurrentShape.IsValidShape; public BaseShape[] Regions => Shapes.OfType().ToArray(); public BaseShape[] ValidRegions => Regions.Where(x => x.IsValidShape).ToArray(); public BaseDrawingShape[] DrawingShapes => Shapes.OfType().ToArray(); public BaseEffectShape[] EffectShapes => Shapes.OfType().ToArray(); public BaseTool[] ToolShapes => Shapes.OfType().ToArray(); private BaseShape currentHoverShape; public BaseShape CurrentHoverShape { get { return currentHoverShape; } private set { if (currentHoverShape != null) { if (PreviousHoverRectangle == Rectangle.Empty || PreviousHoverRectangle != currentHoverShape.Rectangle) { PreviousHoverRectangle = currentHoverShape.Rectangle; } } else { PreviousHoverRectangle = Rectangle.Empty; } currentHoverShape = value; } } public RectangleF PreviousHoverRectangle { get; private set; } public bool IsCurrentHoverShapeValid => CurrentHoverShape != null && CurrentHoverShape.IsValidShape; public bool IsCurrentShapeTypeRegion => IsShapeTypeRegion(CurrentTool); public int StartingStepNumber { get; set; } = 1; public bool IsCreating { get; set; } private bool isMoving; public bool IsMoving { get { return isMoving; } set { if (isMoving != value) { isMoving = value; if (isMoving) { Form.SetHandCursor(true); } else { Form.SetDefaultCursor(); } } } } private bool isPanning; public bool IsPanning { get { return isPanning; } set { if (isPanning != value) { isPanning = value; if (isPanning) { Form.SetHandCursor(true); } else { Form.SetDefaultCursor(); } } } } public bool IsResizing { get; set; } // Is holding Ctrl? public bool IsCtrlModifier { get; private set; } public bool IsCornerMoving { get; private set; } // Is holding Shift? public bool IsProportionalResizing { get; private set; } // Is holding Alt? public bool IsSnapResizing { get; private set; } public bool IsRenderingOutput { get; private set; } public PointF RenderOffset { get; private set; } public bool IsImageModified { get; internal set; } public InputManager InputManager { get; private set; } = new InputManager(); public List Windows { get; set; } public bool WindowCaptureMode { get; set; } public bool IncludeControls { get; set; } public RegionCaptureOptions Options { get; private set; } public AnnotationOptions AnnotationOptions => Options.AnnotationOptions; internal List DrawableObjects { get; private set; } internal ResizeNode[] ResizeNodes { get; private set; } private bool nodesVisible; public bool NodesVisible { get { return nodesVisible; } set { nodesVisible = value; if (!nodesVisible) { foreach (ResizeNode node in ResizeNodes) { node.Visible = false; } } else { BaseShape shape = CurrentShape; if (shape != null) { shape.OnNodePositionUpdate(); shape.OnNodeVisible(); } } } } public bool IsCursorOnObject => DrawableObjects.Any(x => x.HandleMouseInput && x.IsCursorHover); public event Action CurrentShapeChanged; public event Action CurrentShapeTypeChanged; public event Action ShapeCreated; public event Action ImageModified; internal RegionCaptureForm Form { get; private set; } private readonly ImageEditorHistory history; private bool isLeftPressed, isRightPressed, isUpPressed, isDownPressed; private ScrollbarManager scrollbarManager; public ShapeManager(RegionCaptureForm form) { Form = form; Options = form.Options; DrawableObjects = new List(); ResizeNodes = new ResizeNode[9]; for (int i = 0; i < ResizeNodes.Length; i++) { ResizeNode node = new ResizeNode(); node.SetCustomNode(form.CustomNodeImage); DrawableObjects.Add(node); ResizeNodes[i] = node; } ResizeNodes[(int)NodePosition.BottomRight].Order = 10; form.Shown += form_Shown; form.LostFocus += form_LostFocus; form.MouseDown += form_MouseDown; form.MouseUp += form_MouseUp; form.MouseDoubleClick += form_MouseDoubleClick; form.MouseWheel += form_MouseWheel; form.KeyDown += form_KeyDown; form.KeyUp += form_KeyUp; CurrentShape = null; if (form.Mode == RegionCaptureMode.Annotation) { CurrentTool = Options.LastRegionTool; } else if (form.IsEditorMode) { CurrentTool = Options.LastEditorTool; } else { CurrentTool = ShapeType.RegionRectangle; } if (form.IsEditorMode) { scrollbarManager = new ScrollbarManager(form, this); } foreach (ImageEditorControl control in DrawableObjects) { control.MouseDown += (sender, e) => Form.SetHandCursor(true); control.MouseUp += (sender, e) => { if (control.IsCursorHover) { Form.SetHandCursor(false); } else { Form.SetDefaultCursor(); } }; control.MouseEnter += () => Form.SetHandCursor(false); control.MouseLeave += () => Form.SetDefaultCursor(); } history = new ImageEditorHistory(this); } private void OnCurrentShapeChanged(BaseShape shape) { CurrentShapeChanged?.Invoke(shape); } private void OnCurrentShapeTypeChanged(ShapeType shapeType) { CurrentShapeTypeChanged?.Invoke(shapeType); } private void OnShapeCreated(BaseShape shape) { ShapeCreated?.Invoke(shape); } internal void OnImageModified() { OrderStepShapes(); IsImageModified = true; ImageModified?.Invoke(); } private void form_Shown(object sender, EventArgs e) { if (Form.IsAnnotationMode) { CreateToolbar(); } } private void form_LostFocus(object sender, EventArgs e) { ResetModifiers(); } private void form_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { if (!IsCreating) { StartRegionSelection(); } } else if (e.Button == MouseButtons.Middle) { if (Form.IsEditorMode) { StartPanning(); } } } private void form_MouseUp(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { if (IsMoving || IsCreating) { EndRegionSelection(); } } else if (e.Button == MouseButtons.Right) { if (IsCreating) { DeleteCurrentShape(); EndRegionSelection(); } else if (Form.IsAnnotationMode) { RunAction(Options.RegionCaptureActionRightClick); } else if (IsShapeIntersect()) { DeleteIntersectShape(); } else { Form.CloseWindow(); } } else if (e.Button == MouseButtons.Middle) { if (Form.IsEditorMode) { EndPanning(); } else { RunAction(Options.RegionCaptureActionMiddleClick); } } else if (e.Button == MouseButtons.XButton1) { RunAction(Options.RegionCaptureActionX1Click); } else if (e.Button == MouseButtons.XButton2) { RunAction(Options.RegionCaptureActionX2Click); } } private void form_MouseDoubleClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { if (IsCurrentShapeTypeRegion && ValidRegions.Length > 0) { Form.UpdateRegionPath(); Form.CloseWindow(RegionResult.Region); } else if (CurrentShape != null && !IsCreating) { CurrentShape.OnDoubleClicked(); } } } private void form_MouseWheel(object sender, MouseEventArgs e) { if (Control.ModifierKeys == Keys.None) { if (e.Delta > 0) { if (Options.ShowMagnifier) { Options.MagnifierPixelCount = Math.Min(Options.MagnifierPixelCount + 2, RegionCaptureOptions.MagnifierPixelCountMaximum); } else { Options.ShowMagnifier = true; } } else if (e.Delta < 0) { int magnifierPixelCount = Options.MagnifierPixelCount - 2; if (magnifierPixelCount < RegionCaptureOptions.MagnifierPixelCountMinimum) { magnifierPixelCount = RegionCaptureOptions.MagnifierPixelCountMinimum; Options.ShowMagnifier = false; } Options.MagnifierPixelCount = magnifierPixelCount; } if (Form.IsAnnotationMode) { tsmiShowMagnifier.Checked = Options.ShowMagnifier; tslnudMagnifierPixelCount.Content.Value = Options.MagnifierPixelCount; } } } public bool HandleEscape() { // the escape key handling has 3 stages: // 1. initiate exit if region selection is active // 2. if a shape is selected, unselect it // 3. switch to the select tool if a any other tool is active switch (CurrentTool) { case ShapeType.RegionRectangle: case ShapeType.RegionEllipse: case ShapeType.RegionFreehand: return false; } if (CurrentShape != null) { ClearTools(); DeselectCurrentShape(); } if (CurrentTool != ShapeType.ToolSelect) { CurrentTool = ShapeType.ToolSelect; return true; } return false; } private void form_KeyDown(object sender, KeyEventArgs e) { switch (e.KeyCode) { case Keys.ControlKey: if (!IsCtrlModifier && !IsCornerMoving) { if (IsCreating || IsResizing) { IsCornerMoving = true; } else { IsCtrlModifier = true; } } break; case Keys.ShiftKey: IsProportionalResizing = true; break; case Keys.Menu: IsSnapResizing = true; break; case Keys.Left: isLeftPressed = true; break; case Keys.Right: isRightPressed = true; break; case Keys.Up: isUpPressed = true; break; case Keys.Down: isDownPressed = true; break; } switch (e.KeyData) { case Keys.Insert: if (IsCreating) { EndRegionSelection(); } else { StartRegionSelection(); } break; case Keys.Delete: DeleteCurrentShape(); if (IsCreating) { EndRegionSelection(); } break; case Keys.Shift | Keys.Delete: DeleteAllShapes(true); break; } if (!IsCreating) { if (Form.Mode == RegionCaptureMode.Annotation) { switch (e.KeyData) { case Keys.Tab: SwapShapeType(); break; case Keys.NumPad0: CurrentTool = ShapeType.RegionRectangle; break; } } if (Form.IsAnnotationMode) { switch (e.KeyData) { case Keys.M: CurrentTool = ShapeType.ToolSelect; break; case Keys.R: case Keys.NumPad1: CurrentTool = ShapeType.DrawingRectangle; break; case Keys.E: case Keys.NumPad2: CurrentTool = ShapeType.DrawingEllipse; break; case Keys.F: case Keys.NumPad3: CurrentTool = ShapeType.DrawingFreehand; break; case Keys.L: case Keys.NumPad4: CurrentTool = ShapeType.DrawingLine; break; case Keys.A: case Keys.NumPad5: CurrentTool = ShapeType.DrawingArrow; break; case Keys.O: case Keys.NumPad6: CurrentTool = ShapeType.DrawingTextOutline; break; case Keys.T: CurrentTool = ShapeType.DrawingTextBackground; break; case Keys.S: CurrentTool = ShapeType.DrawingSpeechBalloon; break; case Keys.I: case Keys.NumPad7: CurrentTool = ShapeType.DrawingStep; break; case Keys.B: case Keys.NumPad8: CurrentTool = ShapeType.EffectBlur; break; case Keys.P: case Keys.NumPad9: CurrentTool = ShapeType.EffectPixelate; break; case Keys.H: CurrentTool = ShapeType.EffectHighlight; break; case Keys.Control | Keys.D: DuplicateCurrrentShape(true); break; case Keys.Control | Keys.V: PasteFromClipboard(true); break; case Keys.Control | Keys.Z: history.Undo(); break; case Keys.Control | Keys.Y: history.Redo(); break; case Keys.Home: MoveCurrentShapeTop(); break; case Keys.End: MoveCurrentShapeBottom(); break; case Keys.PageUp: MoveCurrentShapeUp(); break; case Keys.PageDown: MoveCurrentShapeDown(); break; } } if (Form.IsEditorMode) { switch (e.KeyData) { case Keys.C: CurrentTool = ShapeType.ToolCrop; break; case Keys.X: CurrentTool = ShapeType.ToolCutOut; break; case Keys.Control | Keys.S: Form.OnSaveImageRequested(); break; case Keys.Control | Keys.Shift | Keys.S: Form.OnSaveImageAsRequested(); break; case Keys.Control | Keys.C: Form.OnCopyImageRequested(); break; case Keys.Control | Keys.U: Form.OnUploadImageRequested(); break; case Keys.Control | Keys.P: Form.OnPrintImageRequested(); break; } } } int speed; if (e.Shift) { speed = RegionCaptureOptions.MoveSpeedMaximum; } else { speed = RegionCaptureOptions.MoveSpeedMinimum; } int x = 0; if (isLeftPressed) { x -= speed; } if (isRightPressed) { x += speed; } int y = 0; if (isUpPressed) { y -= speed; } if (isDownPressed) { y += speed; } if (x != 0 || y != 0) { BaseShape shape = CurrentShape; if (shape == null || IsCreating) { Cursor.Position = Cursor.Position.Add(x, y); } else if (e.Control) { shape.OnResizing(); shape.Resize(x, y, true); } else if (e.Alt) { shape.OnResizing(); shape.Resize(x, y, false); } else { shape.OnMoving(); shape.Move(x, y); } } } private void form_KeyUp(object sender, KeyEventArgs e) { bool wasMoving = isLeftPressed || isRightPressed || isUpPressed || isDownPressed; switch (e.KeyCode) { case Keys.ControlKey: IsCtrlModifier = false; IsCornerMoving = false; break; case Keys.ShiftKey: IsProportionalResizing = false; break; case Keys.Menu: IsSnapResizing = false; break; case Keys.Left: isLeftPressed = false; break; case Keys.Right: isRightPressed = false; break; case Keys.Up: isUpPressed = false; break; case Keys.Down: isDownPressed = false; break; } if (!IsCreating && !IsMoving && wasMoving) { bool isMoving = isLeftPressed || isRightPressed || isUpPressed || isDownPressed; if (!isMoving) { ShapeMoved(); } } } private void ShapeMoved() { if (!IsCreating) { BaseShape shape = CurrentShape; if (shape != null) { shape.OnMoved(); } } } private void RunAction(RegionCaptureAction action) { switch (action) { case RegionCaptureAction.CancelCapture: if (Form.IsEditorMode) { if (Form.ShowExitConfirmation()) { Form.CloseWindow(RegionResult.AnnotateContinueTask); } } else { Form.CloseWindow(); } break; case RegionCaptureAction.RemoveShapeCancelCapture: if (IsShapeIntersect()) { DeleteIntersectShape(); } else if (Form.IsEditorMode) { if (Form.ShowExitConfirmation()) { Form.CloseWindow(RegionResult.AnnotateContinueTask); } } else { Form.CloseWindow(); } break; case RegionCaptureAction.RemoveShape: DeleteIntersectShape(); break; case RegionCaptureAction.SwapToolType: SwapShapeType(); break; case RegionCaptureAction.CaptureFullscreen: Form.CloseWindow(RegionResult.Fullscreen); break; case RegionCaptureAction.CaptureActiveMonitor: Form.CloseWindow(RegionResult.ActiveMonitor); break; case RegionCaptureAction.CaptureLastRegion: if (RegionCaptureForm.LastRegionFillPath != null) { Form.CloseWindow(RegionResult.LastRegion); } break; } } public void Update() { BaseShape shape = CurrentShape; if (shape != null) { shape.OnUpdate(); } UpdateCurrentHoverShape(); UpdateNodes(); if (scrollbarManager != null) { scrollbarManager.Update(); } } public void StartRegionSelection() { if (IsCursorOnObject) { return; } InputManager.Update(Form); // If it's a touch event we don't have the correct point yet, so refresh it now BaseShape shape = GetIntersectShape(); if (shape != null && shape.IsSelectable) // Select shape { DeselectCurrentShape(); if (!IsMoving) { history.CreateShapesMemento(); } IsMoving = true; shape.OnMoving(); CurrentShape = shape; SelectCurrentShape(); } else if (shape == null && CurrentTool == ShapeType.ToolSelect) { ClearTools(); DeselectCurrentShape(); } else if (!IsCreating && CurrentTool != ShapeType.ToolSelect) // Create new shape { ClearTools(); DeselectCurrentShape(); shape = AddShape(); shape.OnCreating(); } } public void EndRegionSelection() { bool wasCreating = IsCreating; bool wasMoving = IsMoving; IsCreating = false; IsMoving = false; BaseShape shape = CurrentShape; if (shape != null) { if (!shape.IsValidShape) { shape.Rectangle = Rectangle.Empty; UpdateCurrentHoverShape(); if (IsCurrentHoverShapeValid) { shape.Rectangle = CurrentHoverShape.Rectangle; } else { DeleteCurrentShape(); shape = null; } } if (shape != null) { if (Options.QuickCrop && IsCurrentShapeTypeRegion) { Form.UpdateRegionPath(); Form.CloseWindow(RegionResult.Region); } else { if (wasCreating) { shape.OnCreated(); OnShapeCreated(shape); SelectCurrentShape(); if (Options.SwitchToSelectionToolAfterDrawing && shape.IsHandledBySelectTool) { CurrentTool = ShapeType.ToolSelect; } } else if (wasMoving) { shape.OnMoved(); SelectCurrentShape(); } } } } } private void StartPanning() { IsPanning = true; Options.ShowEditorPanTip = false; } private void EndPanning() { IsPanning = false; } internal void UpdateObjects(ImageEditorControl[] objects, PointF mousePosition) { if (objects.All(x => !x.IsDragging)) { for (int i = 0; i < objects.Length; i++) { ImageEditorControl obj = objects[i]; if (!IsCtrlModifier && obj.Visible) { obj.IsCursorHover = obj.Rectangle.Contains(mousePosition); if (obj.IsCursorHover) { if (InputManager.IsMousePressed(MouseButtons.Left)) { if (obj is ResizeNode) { history.CreateShapesMemento(); } obj.OnMouseDown(mousePosition.Round()); } for (int j = i + 1; j < objects.Length; j++) { objects[j].IsCursorHover = false; } break; } } else { obj.IsCursorHover = false; } } } else { if (InputManager.IsMouseReleased(MouseButtons.Left)) { foreach (ImageEditorControl obj in objects) { if (obj.IsDragging) { obj.OnMouseUp(mousePosition.Round()); } } } } } internal void UpdateObjects() { ImageEditorControl[] scrollbars = DrawableObjects.Where(x => x is ImageEditorScrollbar).ToArray(); ImageEditorControl[] shapes = DrawableObjects.Except(scrollbars).OrderByDescending(x => x.Order).ToArray(); UpdateObjects(shapes, Form.ScaledClientMousePosition); UpdateObjects(scrollbars, InputManager.ClientMousePosition); } internal void DrawObjects(Graphics g) { if (!IsCtrlModifier) { foreach (ImageEditorControl obj in DrawableObjects) { if (obj.Visible) { obj.OnDraw(g); } } } } private BaseShape AddShape() { BaseShape shape = CreateShape(); AddShape(shape); return shape; } private void AddShape(BaseShape shape) { history.CreateShapesMemento(); Shapes.Add(shape); CurrentShape = shape; } private BaseShape CreateShape() { return CreateShape(CurrentTool); } private BaseShape CreateShape(ShapeType shapeType) { BaseShape shape; switch (shapeType) { default: case ShapeType.RegionRectangle: shape = new RectangleRegionShape(); break; case ShapeType.RegionEllipse: shape = new EllipseRegionShape(); break; case ShapeType.RegionFreehand: shape = new FreehandRegionShape(); break; case ShapeType.DrawingRectangle: shape = new RectangleDrawingShape(); break; case ShapeType.DrawingEllipse: shape = new EllipseDrawingShape(); break; case ShapeType.DrawingFreehand: shape = new FreehandDrawingShape(); break; case ShapeType.DrawingFreehandArrow: shape = new FreehandArrowDrawingShape(); break; case ShapeType.DrawingLine: shape = new LineDrawingShape(); break; case ShapeType.DrawingArrow: shape = new ArrowDrawingShape(); break; case ShapeType.DrawingTextOutline: shape = new TextOutlineDrawingShape(); break; case ShapeType.DrawingTextBackground: shape = new TextDrawingShape(); break; case ShapeType.DrawingSpeechBalloon: shape = new SpeechBalloonDrawingShape(); break; case ShapeType.DrawingStep: shape = new StepDrawingShape(); break; case ShapeType.DrawingMagnify: shape = new MagnifyDrawingShape(); break; case ShapeType.DrawingImage: shape = new ImageFileDrawingShape(); break; case ShapeType.DrawingImageScreen: shape = new ImageScreenDrawingShape(); break; case ShapeType.DrawingSticker: shape = new StickerDrawingShape(); break; case ShapeType.DrawingCursor: shape = new CursorDrawingShape(); break; case ShapeType.DrawingSmartEraser: shape = new SmartEraserDrawingShape(); break; case ShapeType.EffectBlur: shape = new BlurEffectShape(); break; case ShapeType.EffectPixelate: shape = new PixelateEffectShape(); break; case ShapeType.EffectHighlight: shape = new HighlightEffectShape(); break; case ShapeType.ToolCrop: shape = new CropTool(); break; case ShapeType.ToolCutOut: shape = new CutOutTool(); break; } shape.Manager = this; shape.OnConfigLoad(); return shape; } private void UpdateCurrentShape() { BaseShape shape = CurrentShape; if (shape != null) { shape.OnConfigLoad(); shape.OnMoved(); } Form.Resume(); } private void SwapShapeType() { if (Form.Mode == RegionCaptureMode.Annotation) { if (IsCurrentShapeTypeRegion) { CurrentTool = Options.LastAnnotationTool; } else { CurrentTool = Options.LastRegionTool; } } } public PointF SnapPosition(PointF posOnClick, PointF posCurrent) { SizeF currentSize = CaptureHelpers.CreateRectangle(posOnClick, posCurrent).Size; Vector2 vector = new Vector2(currentSize.Width, currentSize.Height); SnapSize snapSize = (from size in Options.SnapSizes let distance = MathHelpers.Distance(vector, new Vector2(size.Width, size.Height)) where distance > 0 && distance < RegionCaptureOptions.SnapDistance orderby distance select size).FirstOrDefault(); if (snapSize != null) { PointF posNew = CaptureHelpers.CalculateNewPosition(posOnClick, posCurrent, snapSize); RectangleF newRect = CaptureHelpers.CreateRectangle(posOnClick, posNew); if (Form.ClientArea.Contains(newRect.Round())) { return posNew; } } return posCurrent; } private void UpdateCurrentHoverShape() { CurrentHoverShape = CheckHover(); } private BaseShape CheckHover() { if (!IsCursorOnObject && !IsCreating && !IsMoving && !IsResizing) { BaseShape shape = GetIntersectShape(); if (shape != null && shape.IsValidShape) { return shape; } else { switch (CurrentTool) { case ShapeType.RegionFreehand: case ShapeType.DrawingFreehand: case ShapeType.DrawingFreehandArrow: case ShapeType.DrawingLine: case ShapeType.DrawingArrow: case ShapeType.DrawingTextOutline: case ShapeType.DrawingTextBackground: case ShapeType.DrawingSpeechBalloon: case ShapeType.DrawingStep: case ShapeType.DrawingImage: case ShapeType.DrawingSticker: case ShapeType.DrawingCursor: case ShapeType.ToolSelect: return null; } if (Options.IsFixedSize && IsCurrentShapeTypeRegion) { PointF location = Form.ScaledClientMousePosition; BaseShape rectangleRegionShape = CreateShape(ShapeType.RegionRectangle); rectangleRegionShape.Rectangle = new RectangleF(new PointF(location.X - (Options.FixedSize.Width / 2), location.Y - (Options.FixedSize.Height / 2)), Options.FixedSize); return rectangleRegionShape; } else { SimpleWindowInfo window = FindSelectedWindow(); if (window != null && !window.Rectangle.IsEmpty) { Rectangle hoverArea = Form.RectangleToClient(window.Rectangle); BaseShape rectangleRegionShape = CreateShape(ShapeType.RegionRectangle); rectangleRegionShape.Rectangle = Rectangle.Intersect(Form.ClientArea, hoverArea); return rectangleRegionShape; } } } } return null; } public SimpleWindowInfo FindSelectedWindow() { if (Windows != null) { return Windows.FirstOrDefault(x => x.Rectangle.Contains(InputManager.MousePosition)); } return null; } public WindowInfo FindSelectedWindowInfo(Point position) { if (Windows != null) { SimpleWindowInfo windowInfo = Windows.FirstOrDefault(x => x.IsWindow && x.Rectangle.Contains(position)); if (windowInfo != null) { return windowInfo.WindowInfo; } } return null; } public Bitmap RenderOutputImage(Bitmap bmp) { return RenderOutputImage(bmp, Point.Empty); } public Bitmap RenderOutputImage(Bitmap bmp, PointF offset) { Bitmap bmpOutput = (Bitmap)bmp.Clone(); bmpOutput.SetResolution(CurrentDPI.X, CurrentDPI.Y); if (DrawingShapes.Length > 0 || EffectShapes.Length > 0) { IsRenderingOutput = true; RenderOffset = offset; MoveAll(-offset.X, -offset.Y); using (Graphics g = Graphics.FromImage(bmpOutput)) { foreach (BaseEffectShape shape in EffectShapes) { if (shape != null) { shape.OnDrawFinal(g, bmpOutput); } } foreach (BaseDrawingShape shape in DrawingShapes) { if (shape != null) { shape.OnDraw(g); } } } MoveAll(offset); RenderOffset = Point.Empty; IsRenderingOutput = false; } return bmpOutput; } private void SelectShape(BaseShape shape) { if (shape != null) { shape.ShowNodes(); if (Options.SwitchToDrawingToolAfterSelection) { CurrentTool = shape.ShapeType; } } } private void SelectCurrentShape() { SelectShape(CurrentShape); } private void SelectIntersectShape() { BaseShape shape = GetIntersectShape(); if (shape != null) { CurrentShape = shape; SelectShape(shape); } } private void DeselectShape(BaseShape shape) { if (shape == CurrentShape) { CurrentShape = null; NodesVisible = false; } } private void DeselectCurrentShape() { DeselectShape(CurrentShape); } public void DeleteShape(BaseShape shape) { if (shape != null) { history.CreateShapesMemento(); shape.Dispose(); Shapes.Remove(shape); DeselectShape(shape); if (shape.ShapeCategory == ShapeCategory.Drawing || shape.ShapeCategory == ShapeCategory.Effect) { OnImageModified(); } UpdateMenu(); } } private void DeleteCurrentShape() { DeleteShape(CurrentShape); } private void DeleteIntersectShape() { DeleteShape(GetIntersectShape()); } private void DeleteAllShapes(bool takeSnapshot = false) { if (Shapes.Count > 0) { if (takeSnapshot) { history.CreateShapesMemento(); } foreach (BaseShape shape in Shapes) { shape.Dispose(); } Shapes.Clear(); DeselectCurrentShape(); OnImageModified(); } } private void ResetModifiers() { IsCtrlModifier = IsCornerMoving = IsProportionalResizing = IsSnapResizing = false; } private void ClearTools() { foreach (BaseTool tool in ToolShapes) { tool.Dispose(); Shapes.Remove(tool); } } public BaseShape GetIntersectShape() { return GetIntersectShape(Form.ScaledClientMousePosition); } public BaseShape GetIntersectShape(PointF position) { if (!IsCtrlModifier) { for (int i = Shapes.Count - 1; i >= 0; i--) { BaseShape shape = Shapes[i]; if (shape.IsSelectable && shape.Intersects(position)) { return shape; } } } return null; } public bool IsShapeIntersect() { return GetIntersectShape() != null; } public void RestoreState(ImageEditorMemento memento) { if (memento != null) { if (memento.Canvas != null) { UpdateCanvas(memento.Canvas); } Shapes = memento.Shapes; ClearTools(); DeselectCurrentShape(); MoveAll(Form.CanvasRectangle.X - memento.CanvasRectangle.X, Form.CanvasRectangle.Y - memento.CanvasRectangle.Y); foreach (BaseEffectShape effect in EffectShapes) { effect.OnMoved(); } OnImageModified(); UpdateMenu(); } } public void MoveShapeBottom(BaseShape shape) { if (shape != null) { for (int i = 0; i < Shapes.Count; i++) { if (Shapes[i] == shape) { Shapes.Move(i, 0); return; } } } } public void MoveCurrentShapeBottom() { MoveShapeBottom(CurrentShape); } public void MoveShapeTop(BaseShape shape) { if (shape != null) { for (int i = 0; i < Shapes.Count; i++) { if (Shapes[i] == shape) { Shapes.Move(i, Shapes.Count - 1); return; } } } } public void MoveCurrentShapeTop() { MoveShapeTop(CurrentShape); } public void MoveShapeDown(BaseShape shape) { if (shape != null) { for (int i = 1; i < Shapes.Count; i++) { if (Shapes[i] == shape) { Shapes.Move(i, --i); return; } } } } public void MoveCurrentShapeDown() { MoveShapeDown(CurrentShape); } public void MoveShapeUp(BaseShape shape) { if (shape != null) { for (int i = 0; i < Shapes.Count - 1; i++) { if (Shapes[i] == shape) { Shapes.Move(i, ++i); return; } } } } public void MoveCurrentShapeUp() { MoveShapeUp(CurrentShape); } public void MoveAll(float x, float y) { if (x != 0 || y != 0) { foreach (BaseShape shape in Shapes) { shape.Move(x, y); } } } public void MoveAll(PointF offset) { MoveAll(offset.X, offset.Y); } public void CollapseAllHorizontal(float x, float width) { float x2 = x + width; if (width <= 0) return; List toDelete = new List(); foreach (BaseShape shape in Shapes) { RectangleF sr = shape.Rectangle; if (sr.Left < x) { if (sr.Right <= x) { // case 1: entirely before the cut, no action needed } else if (sr.Right < x2) { // case 2: end reaches into the cut, shorten shape to end at x shape.Rectangle = new RectangleF(sr.X, sr.Y, x - sr.X, sr.Height); } else { // case 3: end reaches over the cut, shorten shape by width, keeping left shape.Rectangle = new RectangleF(sr.X, sr.Y, sr.Width - width, sr.Height); } } else if (sr.Left < x2) { if (sr.Right <= x2) { // case 4: entirely inside the cut, delete the shape toDelete.Add(shape); } else { // case 5: beginning reaches into the cut, shorten shape by difference between shape left and x2 shape.Rectangle = new RectangleF(x, sr.Y, sr.Right - x2, sr.Height); } } else { // case 6: entirely after the cut, offset shape by width shape.Rectangle = new RectangleF(sr.X - width, sr.Y, sr.Width, sr.Height); } } foreach (BaseShape shape in toDelete) { DeleteShape(shape); } } public void CollapseAllVertical(float y, float height) { float y2 = y + height; if (height <= 0) return; List toDelete = new List(); foreach (BaseShape shape in Shapes) { RectangleF sr = shape.Rectangle; if (sr.Top < y) { if (sr.Bottom <= y) { // case 1: entirely before the cut, no action needed } else if (sr.Bottom < y2) { // case 2: end reaches into the cut, shorten shape to end at x shape.Rectangle = new RectangleF(sr.X, sr.Y, sr.Width, y - sr.Y); } else { // case 3: end reaches over the cut, shorten shape by width, keeping left shape.Rectangle = new RectangleF(sr.X, sr.Y, sr.Width, sr.Height - height); } } else if (sr.Top < y2) { if (sr.Bottom <= y2) { // case 4: entirely inside the cut, delete the shape toDelete.Add(shape); } else { // case 5: beginning reaches into the cut, shorten shape by difference between shape left and x2 shape.Rectangle = new RectangleF(sr.X, y, sr.Width, sr.Bottom - y2); } } else { // case 6: entirely after the cut, offset shape by width shape.Rectangle = new RectangleF(sr.X, sr.Y - height, sr.Width, sr.Height); } } foreach (BaseShape shape in toDelete) { DeleteShape(shape); } } public void RemoveOutsideShapes() { foreach (BaseShape shape in Shapes.ToArray()) { if (!Form.CanvasRectangle.IntersectsWith(shape.Rectangle)) { shape.Remove(); } } } private bool IsShapeTypeRegion(ShapeType shapeType) { switch (shapeType) { case ShapeType.RegionRectangle: case ShapeType.RegionEllipse: case ShapeType.RegionFreehand: return true; } return false; } private void UpdateNodes() { BaseShape shape = CurrentShape; if (shape != null && NodesVisible) { if (InputManager.IsMouseDown(MouseButtons.Left)) { shape.OnNodeUpdate(); } else if (IsResizing) { IsResizing = false; shape.OnResized(); } shape.OnNodePositionUpdate(); } } public void OrderStepShapes() { int i = StartingStepNumber; foreach (StepDrawingShape shape in Shapes.OfType()) { shape.Number = i++; } } private void DuplicateCurrrentShape(bool insertMousePosition) { BaseShape shape = CurrentShape; if (shape != null && shape.IsHandledBySelectTool) { BaseShape shapeCopy = shape.Duplicate(); if (shapeCopy != null) { if (insertMousePosition) { shapeCopy.MoveAbsolute(Form.ScaledClientMousePosition, true); } else { shapeCopy.Move(10, 10); } shapeCopy.OnMoved(); AddShape(shapeCopy); SelectCurrentShape(); } } } private void PasteFromClipboard(bool insertMousePosition) { if (ClipboardHelpers.ContainsImage()) { Bitmap bmp = ClipboardHelpers.GetImage(); InsertImage(bmp); } else if (ClipboardHelpers.ContainsFileDropList()) { string[] files = ClipboardHelpers.GetFileDropList(); if (files != null) { string imageFilePath = files.FirstOrDefault(x => FileHelpers.IsImageFile(x)); if (!string.IsNullOrEmpty(imageFilePath)) { Bitmap bmp = ImageHelpers.LoadImage(imageFilePath); InsertImage(bmp); } } } else if (ClipboardHelpers.ContainsText()) { string text = ClipboardHelpers.GetText(); if (!string.IsNullOrEmpty(text)) { PointF pos; if (insertMousePosition) { pos = Form.ScaledClientMousePosition; } else { pos = Form.ClientArea.Center(); } CurrentTool = ShapeType.DrawingTextBackground; TextDrawingShape shape = (TextDrawingShape)CreateShape(ShapeType.DrawingTextBackground); shape.Rectangle = new RectangleF(pos.X, pos.Y, 1, 1); shape.Text = text.Trim(); shape.OnCreated(); AddShape(shape); SelectCurrentShape(); } } } public void AddCursor(Bitmap bmpCursor, Point position) { CursorDrawingShape shape = (CursorDrawingShape)CreateShape(ShapeType.DrawingCursor); shape.UpdateCursor(bmpCursor, position); Shapes.Add(shape); } public void DrawRegionArea(Graphics g, RectangleF rect, bool isAnimated, bool showAreaInfo = false) { Form.DrawRegionArea(g, rect, isAnimated); if (showAreaInfo) { string areaText = Form.GetAreaText(rect); Form.DrawAreaText(g, areaText, rect); } } public void UpdateCanvas(Bitmap canvas, bool centerCanvas = true) { Form.InitBackground(canvas, centerCanvas); foreach (BaseEffectShape effect in EffectShapes) { effect.OnMoved(); } OnImageModified(); } public void CropArea(RectangleF rect) { Bitmap bmp = CropImage(rect, true); if (bmp != null) { history.CreateCanvasMemento(); MoveAll(Form.CanvasRectangle.X - rect.X, Form.CanvasRectangle.Y - rect.Y); UpdateCanvas(bmp); } } public Bitmap CropImage(RectangleF rect, bool onlyIfSizeDifferent = false) { rect = CaptureHelpers.ScreenToClient(rect.Round()); PointF offset = CaptureHelpers.ScreenToClient(Form.CanvasRectangle.Location.Round()); rect.X -= offset.X; rect.Y -= offset.Y; Rectangle cropRect = Rectangle.Intersect(new Rectangle(0, 0, Form.Canvas.Width, Form.Canvas.Height), rect.Round()); if (cropRect.IsValid() && (!onlyIfSizeDifferent || cropRect.Size != Form.Canvas.Size)) { return ImageHelpers.CropBitmap(Form.Canvas, cropRect); } return null; } public void CutOut(RectangleF rect) { history.CreateCanvasMemento(); bool isHorizontal = rect.Width > rect.Height; RectangleF adjustedRect = CaptureHelpers.ScreenToClient(rect.Round()); PointF offset = CaptureHelpers.ScreenToClient(Form.CanvasRectangle.Location.Round()); adjustedRect.X -= offset.X; adjustedRect.Y -= offset.Y; Rectangle cropRect = Rectangle.Intersect(new Rectangle(0, 0, Form.Canvas.Width, Form.Canvas.Height), adjustedRect.Round()); if (isHorizontal && cropRect.Width > 0) { CollapseAllHorizontal(rect.X, rect.Width); UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Horizontal, cropRect.X, cropRect.Width, AnnotationOptions.CutOutEffectType, AnnotationOptions.CutOutEffectSize, AnnotationOptions.CutOutBackgroundColor)); } else if (!isHorizontal && cropRect.Height > 0) { CollapseAllVertical(rect.Y, rect.Height); UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Vertical, cropRect.Y, cropRect.Height, AnnotationOptions.CutOutEffectType, AnnotationOptions.CutOutEffectSize, AnnotationOptions.CutOutBackgroundColor)); } } public Color GetColor(Bitmap bmp, Point pos) { if (bmp != null) { Point position = CaptureHelpers.ScreenToClient(pos); Point offset = CaptureHelpers.ScreenToClient(Form.CanvasRectangle.Location.Round()); position.X -= offset.X; position.Y -= offset.Y; if (position.X.IsBetween(0, bmp.Width - 1) && position.Y.IsBetween(0, bmp.Height - 1)) { return bmp.GetPixel(position.X, position.Y); } } return Color.Empty; } public Color GetCurrentColor(Bitmap bmp) { return GetColor(bmp, Form.ScaledClientMousePosition.Round()); } public Color GetCurrentColor() { return GetCurrentColor(Form.Canvas); } public void NewImage() { Form.Pause(); Bitmap bmp = NewImageForm.CreateNewImage(Options, Form); Form.Resume(); if (bmp != null) { Form.ImageFilePath = ""; history.CreateCanvasMemento(); DeleteAllShapes(); UpdateMenu(); UpdateCanvas(bmp); } } private void OpenImageFile() { Form.Pause(); string filePath = ImageHelpers.OpenImageFileDialog(Form); Form.Resume(); LoadImageFile(filePath); } private void LoadImageFile(string filePath) { if (!string.IsNullOrEmpty(filePath)) { Bitmap bmp = ImageHelpers.LoadImage(filePath); if (bmp != null) { Form.ImageFilePath = filePath; history.CreateCanvasMemento(); DeleteAllShapes(); UpdateMenu(); UpdateCanvas(bmp); } } } private void InsertImageFile() { Form.Pause(); string filePath = ImageHelpers.OpenImageFileDialog(Form); Form.Resume(); if (!string.IsNullOrEmpty(filePath)) { Bitmap bmp = ImageHelpers.LoadImage(filePath); InsertImage(bmp); } } private void InsertImageFromScreen() { Bitmap bmp; try { Form.Pause(); Form.Hide(); menuForm.Hide(); Thread.Sleep(250); bmp = RegionCaptureTasks.GetRegionImage(Options); } finally { Form.Show(); menuForm.Show(); Form.Resume(); } InsertImage(bmp); } private void InsertImage(Image img) { if (img != null) { PointF pos; bool centerImage; using (ImageInsertForm imageInsertForm = new ImageInsertForm()) { imageInsertForm.ShowDialog(Form); switch (imageInsertForm.ImageInsertMethod) { default: img.Dispose(); return; case ImageInsertMethod.Center: pos = Form.ClientArea.Center(); centerImage = true; break; case ImageInsertMethod.CanvasExpandDown: pos = new PointF(Form.CanvasRectangle.X, Form.CanvasRectangle.Bottom); centerImage = false; ChangeCanvasSize(new Padding(0, 0, (int)Math.Round(Math.Max(0, img.Width - Form.CanvasRectangle.Width)), img.Height), Options.EditorCanvasColor); break; case ImageInsertMethod.CanvasExpandRight: pos = new PointF(Form.CanvasRectangle.Right, Form.CanvasRectangle.Y); centerImage = false; ChangeCanvasSize(new Padding(0, 0, img.Width, (int)Math.Round(Math.Max(0, img.Height - Form.CanvasRectangle.Height))), Options.EditorCanvasColor); break; } } CurrentTool = ShapeType.DrawingImage; ImageDrawingShape shape = (ImageDrawingShape)CreateShape(ShapeType.DrawingImage); shape.Rectangle = new RectangleF(pos.X, pos.Y, 1, 1); shape.SetImage(img, centerImage); shape.OnCreated(); AddShape(shape); SelectCurrentShape(); } } private void ChangeImageSize() { Form.Pause(); Size oldSize = Form.Canvas.Size; using (ImageSizeForm imageSizeForm = new ImageSizeForm(oldSize, Options.ImageEditorResizeInterpolationMode)) { if (imageSizeForm.ShowDialog(Form) == DialogResult.OK) { Size size = imageSizeForm.ImageSize; Options.ImageEditorResizeInterpolationMode = imageSizeForm.InterpolationMode; if (size != oldSize) { history.CreateCanvasMemento(); InterpolationMode interpolationMode = ImageHelpers.GetInterpolationMode(Options.ImageEditorResizeInterpolationMode); Bitmap bmp = ImageHelpers.ResizeImage(Form.Canvas, size, interpolationMode); if (bmp != null) { UpdateCanvas(bmp); } } } } Form.Resume(); } private void ChangeCanvasSize() { Form.Pause(); using (CanvasSizeForm canvasSizeForm = new CanvasSizeForm(Padding.Empty, Options.EditorCanvasColor)) { if (canvasSizeForm.ShowDialog(Form) == DialogResult.OK) { Padding canvas = canvasSizeForm.Canvas; Options.EditorCanvasColor = canvasSizeForm.CanvasColor; Bitmap bmp = ImageHelpers.AddCanvas(Form.Canvas, canvas, Options.EditorCanvasColor); if (bmp != null) { history.CreateCanvasMemento(); MoveAll(canvas.Left, canvas.Top); UpdateCanvas(bmp); } } } Form.Resume(); } public void AutoResizeCanvas() { RectangleF canvas = Form.CanvasRectangle; RectangleF combinedImageRectangle = Shapes.OfType().Select(x => x.Rectangle).Combine(); if (!canvas.Contains(combinedImageRectangle)) { Padding margin = new Padding((int)Math.Round(Math.Max(0, canvas.X - combinedImageRectangle.X)), (int)Math.Round(Math.Max(0, canvas.Y - combinedImageRectangle.Y)), (int)Math.Round(Math.Max(0, combinedImageRectangle.Right - canvas.Right)), (int)Math.Round(Math.Max(0, combinedImageRectangle.Bottom - canvas.Bottom))); ChangeCanvasSize(margin, Options.EditorCanvasColor); } } private void ChangeCanvasSize(Padding margin, Color canvasColor) { Bitmap bmp = ImageHelpers.AddCanvas(Form.Canvas, margin, canvasColor); if (bmp != null) { history.CreateCanvasMemento(); Form.CanvasRectangle = Form.CanvasRectangle.LocationOffset(-margin.Left, -margin.Top); UpdateCanvas(bmp, false); } } private void AddCropTool() { CurrentTool = ShapeType.ToolCrop; CropTool tool = (CropTool)CreateShape(ShapeType.ToolCrop); tool.Rectangle = Form.CanvasRectangle; tool.OnCreated(); AddShape(tool); SelectCurrentShape(); } private void AutoCropImage() { Rectangle source = new Rectangle(0, 0, Form.Canvas.Width, Form.Canvas.Height); RectangleF crop; using (Bitmap resultImage = Form.GetResultImage()) { crop = ImageHelpers.FindAutoCropRectangle(resultImage); } if (source != crop && crop.X >= 0 && crop.Y >= 0 && crop.Width > 0 && crop.Height > 0) { CurrentTool = ShapeType.ToolCrop; CropTool tool = (CropTool)CreateShape(ShapeType.ToolCrop); tool.Rectangle = crop.LocationOffset(Form.CanvasRectangle.Location); tool.OnCreated(); AddShape(tool); SelectCurrentShape(); } } private void RotateImage(RotateFlipType type) { history.CreateCanvasMemento(); Bitmap bmp = (Bitmap)Form.Canvas.Clone(); bmp.RotateFlip(type); UpdateCanvas(bmp); } private void AddImageEffects() { Form.Pause(); using (ImageEffectsForm imageEffectsForm = new ImageEffectsForm(Form.Canvas, Options.ImageEffectPresets, Options.SelectedImageEffectPreset)) { imageEffectsForm.EditorMode(); bool applyEffect = imageEffectsForm.ShowDialog(Form) == DialogResult.OK; Options.SelectedImageEffectPreset = imageEffectsForm.SelectedPresetIndex; if (applyEffect) { ImageEffectPreset preset = imageEffectsForm.Presets.ReturnIfValidIndex(Options.SelectedImageEffectPreset); if (preset != null) { Bitmap bmp = preset.ApplyEffects(Form.Canvas); if (bmp != null) { history.CreateCanvasMemento(); UpdateCanvas(bmp); } } } } Form.Resume(); } private bool PickColor(Color currentColor, out Color newColor) { Func openScreenColorPicker = null; if (Form.IsFullscreen) { openScreenColorPicker = () => { using (Bitmap canvas = Form.Canvas.CloneSafe()) { return RegionCaptureTasks.GetPointInfo(Options, canvas); } }; } else { openScreenColorPicker = () => RegionCaptureTasks.GetPointInfo(Options); } return ColorPickerForm.PickColor(currentColor, out newColor, Form, openScreenColorPicker, Options.ColorPickerOptions); } public void Dispose() { DeleteAllShapes(); history.Dispose(); } } }