/src/tools/ShapeTool.cs
C# | 651 lines | 478 code | 105 blank | 68 comment | 81 complexity | 1cc84b4bf7f7340ae416a2790c684c78 MD5 | raw file
- /////////////////////////////////////////////////////////////////////////////////
- // Paint.NET //
- // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
- // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
- // See src/Resources/Files/License.txt for full licensing and attribution //
- // details. //
- // . //
- /////////////////////////////////////////////////////////////////////////////////
-
- using PaintDotNet.Base;
- using PaintDotNet.HistoryMementos;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Drawing.Drawing2D;
- using System.Windows.Forms;
-
- namespace PaintDotNet.Tools
- {
- /// <summary>
- /// Allows the user to draw a shape that can be defined using two points on the canvas.
- /// The user clicks and drags between two points to define the area that bounds the shape.
- /// </summary>
- internal abstract class ShapeTool
- : Tool
- {
- private const char DefaultShortcut = 'o';
- private bool _moveOriginMode;
- private PointF _lastXY;
- private bool mouseDown;
- private MouseButtons _mouseButton;
- private BitmapLayer _bitmapLayer;
- private RenderArgs _renderArgs;
- private PdnRegion _interiorSaveRegion;
- private PdnRegion _outlineSaveRegion;
- private List<PointF> _points;
- private PdnRegion _lastDrawnRegion;
- private Cursor _cursorMouseUp;
- private Cursor _cursorMouseDown;
- private bool _shapeWasCommited = true;
- private CompoundHistoryMemento _chaAlreadyOnStack;
- private bool _useDashStyle; // if set to false, then the DashStyle will always be forced to DashStyle.Flat
- private ShapeDrawType _forcedShapeDrawType = ShapeDrawType.Both;
-
- protected override bool SupportsInk
- {
- get
- {
- return true;
- }
- }
-
- // This is for shapes that should only be draw in one ShapeDrawType
- // The line shape, for instance, should only ever be drawn in ShapeDrawType.Outline
- protected bool ForceShapeDrawType { get; set; }
-
- protected ShapeDrawType ForcedShapeDrawType
- {
- get
- {
- return _forcedShapeDrawType;
- }
-
- set
- {
- _forcedShapeDrawType = value;
- }
- }
-
- protected bool UseDashStyle
- {
- get
- {
- return _useDashStyle;
- }
-
- set
- {
- _useDashStyle = value;
- }
- }
-
- /// <summary>
- /// Different shapes may not require all the points given to them, and as such
- /// if the user is drawing for a long time there may be lots of memory that's
- /// allocated that doesn't need to be. So before CreateShapePath is called,
- /// this method is called first.
- /// For example, the LineTool would return a new array containing only the
- /// first and last points.
- /// It is ok to return the same array that was passed in, even if it is modified.
- /// </summary>
- /// <param name="points">A list containing PointF instances.</param>
- /// <param name="trimThesePoints"></param>
- /// <returns></returns>
- protected virtual List<PointF> TrimShapePath(List<PointF> trimThesePoints)
- {
- return trimThesePoints;
- }
-
- /// <summary>
- /// Override this function to return an "optimized" region that encompasses
- /// the shape's outline. For example, a circle would return a list of rectangles
- /// that traces the outline. This is necessary because normally simplification
- /// will produce a region that, for a circle's outline, encompasses its
- /// interior as well. If you return null, then the default simplification
- /// algorithm will be used.
- /// </summary>
- /// <param name="points"></param>
- /// <param name="optimizeThesePoints"></param>
- /// <param name="path"></param>
- /// <returns></returns>
- protected virtual RectangleF[] GetOptimizedShapeOutlineRegion(PointF[] optimizeThesePoints, PdnGraphicsPath path)
- {
- return null;
- }
-
- // Implement this!
- protected abstract PdnGraphicsPath CreateShapePath(PointF[] shapePoints);
-
- protected override void OnActivate()
- {
- base.OnActivate();
-
- _outlineSaveRegion = null;
- _interiorSaveRegion = null;
-
- // creates a bitmap layer from the active layer
- _bitmapLayer = (BitmapLayer)ActiveLayer;
-
- // create Graphics object
- _renderArgs = new RenderArgs(_bitmapLayer.Surface);
-
- _lastDrawnRegion = new PdnRegion();
- _lastDrawnRegion.MakeEmpty();
- }
-
- protected override void OnDeactivate()
- {
- base.OnDeactivate();
-
- if (mouseDown)
- {
- var lastPoint = _points[_points.Count - 1];
- OnStylusUp(new StylusEventArgs(_mouseButton, 0, lastPoint.X, lastPoint.Y, 0));
- }
-
- if (!_shapeWasCommited)
- {
- CommitShape();
- }
-
- _bitmapLayer = null;
-
- if (_renderArgs != null)
- {
- _renderArgs.Dispose();
- _renderArgs = null;
- }
-
- if (_outlineSaveRegion != null)
- {
- _outlineSaveRegion.Dispose();
- _outlineSaveRegion = null;
- }
-
- if (_interiorSaveRegion != null)
- {
- _interiorSaveRegion.Dispose();
- _interiorSaveRegion = null;
- }
-
- _points = null;
- }
-
- protected virtual void OnShapeBegin()
- {
- }
-
- /// <summary>
- /// Called when the shape is finished being traced by the default input handlers.
- /// </summary>
- /// <remarks>Do not call the base implementation of this method if you are overriding it.</remarks>
- /// <returns>true to commit the shape immediately</returns>
- protected virtual bool OnShapeEnd()
- {
- return true;
- }
-
- protected override void OnStylusDown(StylusEventArgs e)
- {
- base.OnStylusDown(e);
-
- if (!_shapeWasCommited)
- {
- CommitShape();
- }
-
- ClearSavedMemory();
- ClearSavedRegion();
-
- _cursorMouseUp = Cursor;
- Cursor = _cursorMouseDown;
-
- if (mouseDown && e.Button == _mouseButton)
- {
- return;
- }
-
- if (mouseDown)
- {
- _moveOriginMode = true;
- _lastXY = new PointF(e.Fx, e.Fy);
- OnStylusMove(e);
- }
- else if (((e.Button & MouseButtons.Left) == MouseButtons.Left) ||
- ((e.Button & MouseButtons.Right) == MouseButtons.Right))
- {
- // begin new shape
- _shapeWasCommited = false;
-
- OnShapeBegin();
-
- mouseDown = true;
- _mouseButton = e.Button;
-
- using (PdnRegion clipRegion = Selection.CreateRegion())
- {
- _renderArgs.Graphics.SetClip(clipRegion.GetRegionReadOnly(), CombineMode.Replace);
- }
-
- // reset the points we're drawing!
- _points = new List<PointF>();
-
- OnStylusMove(e);
- }
- }
-
- protected override void OnStylusMove(StylusEventArgs e)
- {
- base.OnStylusMove (e);
-
- if (_moveOriginMode)
- {
- var delta = new SizeF(e.Fx - _lastXY.X, e.Fy - _lastXY.Y);
-
- for (int i = 0; i < _points.Count; ++i)
- {
- var ptF = _points[i];
- ptF.X += delta.Width;
- ptF.Y += delta.Height;
- _points[i] = ptF;
- }
-
- _lastXY = new PointF(e.Fx, e.Fy);
- }
- else if (mouseDown && ((e.Button & _mouseButton) != MouseButtons.None))
- {
- var mouseXY = new PointF(e.Fx, e.Fy);
- _points.Add(mouseXY);
- }
- }
-
- public virtual PixelOffsetMode GetPixelOffsetMode()
- {
- return PixelOffsetMode.Half;
- }
-
- protected List<PointF> GetTrimmedShapePath()
- {
- var pointsCopy = new List<PointF>(_points);
- pointsCopy = TrimShapePath(pointsCopy);
- return pointsCopy;
- }
-
- protected void SetShapePath(List<PointF> newPoints)
- {
- _points = newPoints;
- }
-
- protected void RenderShape()
- {
- // create the Pen we will use to draw with
- Pen outlinePen = null;
- Brush interiorBrush = null;
- PenInfo pi = AppEnvironment.PenInfo;
- BrushInfo bi = AppEnvironment.BrushInfo;
-
- ColorBgra primary = AppEnvironment.PrimaryColor;
- ColorBgra secondary = AppEnvironment.SecondaryColor;
-
- if (!ForceShapeDrawType && AppEnvironment.ShapeDrawType == ShapeDrawType.Interior)
- {
- Utility.Swap(ref primary, ref secondary);
- }
-
- // Initialize pens and brushes to the correct colors
- if ((_mouseButton & MouseButtons.Left) == MouseButtons.Left)
- {
- outlinePen = pi.CreatePen(AppEnvironment.BrushInfo, primary.ToColor(), secondary.ToColor());
- interiorBrush = bi.CreateBrush(secondary.ToColor(), primary.ToColor());
- }
- else if ((_mouseButton & MouseButtons.Right) == MouseButtons.Right)
- {
- outlinePen = pi.CreatePen(AppEnvironment.BrushInfo, secondary.ToColor(), primary.ToColor());
- interiorBrush = bi.CreateBrush(primary.ToColor(), secondary.ToColor());
- }
-
- if (!_useDashStyle)
- {
- if (outlinePen != null) outlinePen.DashStyle = DashStyle.Solid;
- }
-
- if (outlinePen != null)
- {
- outlinePen.LineJoin = LineJoin.MiterClipped;
- outlinePen.MiterLimit = 2;
- }
-
- // redraw the old saveSurface
- if (_interiorSaveRegion != null)
- {
- RestoreRegion(_interiorSaveRegion);
- _interiorSaveRegion.Dispose();
- _interiorSaveRegion = null;
- }
-
- if (_outlineSaveRegion != null)
- {
- RestoreRegion(_outlineSaveRegion);
- _outlineSaveRegion.Dispose();
- _outlineSaveRegion = null;
- }
-
- // anti-aliasing? Don't mind if I do
- _renderArgs.Graphics.SmoothingMode = AppEnvironment.AntiAliasing ? SmoothingMode.AntiAlias : SmoothingMode.None;
-
- // also set the pixel offset mode
- _renderArgs.Graphics.PixelOffsetMode = GetPixelOffsetMode();
-
- // figure out how we're going to draw
-
- ShapeDrawType drawType = ForceShapeDrawType ? ForcedShapeDrawType : AppEnvironment.ShapeDrawType;
-
- // get the region we want to save
- _points = TrimShapePath(_points);
- PointF[] pointsArray = _points.ToArray();
- PdnGraphicsPath shapePath = CreateShapePath(pointsArray);
-
- if (shapePath != null)
- {
- // create non-optimized interior region
- var interiorRegion = new PdnRegion(shapePath);
-
- // create non-optimized outline region
- PdnRegion outlineRegion;
-
- using (var outlinePath = shapePath.Clone())
- {
- try
- {
- outlinePath.Widen(outlinePen);
- outlineRegion = new PdnRegion(outlinePath);
- }
-
- // Sometimes GDI+ gets cranky if we have a very small shape (e.g. all points
- // are coincident).
- catch (OutOfMemoryException)
- {
- outlineRegion = new PdnRegion(shapePath);
- }
- }
-
- // create optimized outlineRegion for purposes of rendering, if it is possible to do so
- // shapes will often provide an "optimized" region that circumvents the fact that
- // we'd otherwise get a region that encompasses the outline *and* the interior, thus
- // slowing rendering significantly in many cases.
- RectangleF[] optimizedOutlineRegion = GetOptimizedShapeOutlineRegion(pointsArray, shapePath);
- PdnRegion invalidOutlineRegion;
-
- if (optimizedOutlineRegion != null)
- {
- if (outlinePen != null)
- Utility.InflateRectanglesInPlace(optimizedOutlineRegion, (int)(outlinePen.Width + 2));
- invalidOutlineRegion = Utility.RectanglesToRegion(optimizedOutlineRegion);
- }
- else
- {
- invalidOutlineRegion = Utility.SimplifyAndInflateRegion(outlineRegion, Utility.DefaultSimplificationFactor, (int)(outlinePen.Width + 2));
- }
-
- // create optimized interior region
- PdnRegion invalidInteriorRegion = Utility.SimplifyAndInflateRegion(interiorRegion, Utility.DefaultSimplificationFactor, 3);
-
- var invalidRegion = new PdnRegion();
- invalidRegion.MakeEmpty();
-
- // set up alpha blending
- _renderArgs.Graphics.CompositingMode = AppEnvironment.GetCompositingMode();
-
- SaveRegion(invalidOutlineRegion, invalidOutlineRegion.GetBoundsInt());
- _outlineSaveRegion = invalidOutlineRegion;
- if ((drawType & ShapeDrawType.Outline) != 0)
- {
- shapePath.Draw(_renderArgs.Graphics, outlinePen);
- }
-
- invalidRegion.Union(invalidOutlineRegion);
-
- // draw shape
- if ((drawType & ShapeDrawType.Interior) != 0)
- {
- SaveRegion(invalidInteriorRegion, invalidInteriorRegion.GetBoundsInt());
- _interiorSaveRegion = invalidInteriorRegion;
- if (interiorBrush != null) _renderArgs.Graphics.FillPath(interiorBrush, shapePath);
- invalidRegion.Union(invalidInteriorRegion);
- }
- else
- {
- invalidInteriorRegion.Dispose();
- invalidInteriorRegion = null;
- }
-
- _bitmapLayer.Invalidate(invalidRegion);
-
- invalidRegion.Dispose();
- invalidRegion = null;
-
- outlineRegion.Dispose();
- outlineRegion = null;
-
- interiorRegion.Dispose();
- interiorRegion = null;
- }
-
- Update();
-
- if (shapePath != null)
- {
- shapePath.Dispose();
- shapePath = null;
- }
-
- if (outlinePen != null) outlinePen.Dispose();
- if (interiorBrush != null) interiorBrush.Dispose();
- }
-
- protected override void OnMouseMove(MouseEventArgs e)
- {
- base.OnMouseMove(e);
-
- // if mouse button not down then leave function
- if (mouseDown && ((e.Button & _mouseButton) != MouseButtons.None))
- {
- RenderShape();
- }
- }
-
- protected override void OnKeyDown(KeyEventArgs e)
- {
- if (mouseDown)
- {
- RenderShape();
- }
-
- base.OnKeyDown(e);
- }
-
- protected override void OnKeyUp(KeyEventArgs e)
- {
- if (mouseDown)
- {
- RenderShape();
- }
-
- base.OnKeyUp(e);
- }
-
- protected virtual void OnShapeCommitting()
- {
- }
-
- protected void CommitShape()
- {
- OnShapeCommitting();
-
- mouseDown = false;
-
- var has = new ArrayList();
- PdnRegion activeRegion = Selection.CreateRegion();
-
- if (_outlineSaveRegion != null)
- {
- using (PdnRegion clipTest = activeRegion.Clone())
- {
- clipTest.Intersect(_outlineSaveRegion);
-
- if (!clipTest.IsEmpty())
- {
- var bha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace,
- ActiveLayerIndex, _outlineSaveRegion, ScratchSurface);
-
- has.Add(bha);
- _outlineSaveRegion.Dispose();
- _outlineSaveRegion = null;
- }
- }
- }
-
- if (_interiorSaveRegion != null)
- {
- using (PdnRegion clipTest = activeRegion.Clone())
- {
- clipTest.Intersect(_interiorSaveRegion);
-
- if (!clipTest.IsEmpty())
- {
- var bha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace,
- ActiveLayerIndex, _interiorSaveRegion, ScratchSurface);
-
- has.Add(bha);
- _interiorSaveRegion.Dispose();
- _interiorSaveRegion = null;
- }
- }
- }
-
- if (has.Count > 0)
- {
- var cha = new CompoundHistoryMemento(Name, Image, (HistoryMemento[])has.ToArray(typeof(HistoryMemento)));
-
- if (_chaAlreadyOnStack == null)
- {
- HistoryStack.PushNewMemento(cha);
- }
- else
- {
- _chaAlreadyOnStack.PushNewAction(cha);
- _chaAlreadyOnStack = null;
- }
- }
-
- activeRegion.Dispose();
- _points = null;
- Update();
- _shapeWasCommited = true;
- }
-
- protected override void OnStylusUp(StylusEventArgs e)
- {
- base.OnStylusUp(e);
-
- Cursor = _cursorMouseUp;
-
- if (_moveOriginMode)
- {
- _moveOriginMode = false;
- }
- else if (mouseDown)
- {
- bool doCommit = OnShapeEnd();
-
- if (doCommit)
- {
- CommitShape();
- }
- else
- {
- // place a 'sentinel' history action on the stack that will be filled in later
- var cha = new CompoundHistoryMemento(Name, Image, new List<HistoryMemento>());
- HistoryStack.PushNewMemento(cha);
- _chaAlreadyOnStack = cha;
- }
- }
- }
-
- protected ShapeTool(DocumentWorkspace documentWorkspace,
- ImageResource toolBarImage,
- string name,
- string helpText)
- : this(documentWorkspace,
- toolBarImage,
- name,
- helpText,
- DefaultShortcut,
- ToolBarConfigItems.None,
- ToolBarConfigItems.None)
- {
- }
-
- protected ShapeTool(DocumentWorkspace documentWorkspace,
- ImageResource toolBarImage,
- string name,
- string helpText,
- ToolBarConfigItems toolBarConfigItemsInclude,
- ToolBarConfigItems toolBarConfigItemsExclude)
- : this(documentWorkspace,
- toolBarImage,
- name,
- helpText,
- DefaultShortcut,
- toolBarConfigItemsInclude,
- toolBarConfigItemsExclude)
- {
- }
-
- protected ShapeTool(DocumentWorkspace documentWorkspace,
- ImageResource toolBarImage,
- string name,
- string helpText,
- char hotKey,
- ToolBarConfigItems toolBarConfigItemsInclude,
- ToolBarConfigItems toolBarConfigItemsExclude)
- : base(documentWorkspace,
- toolBarImage,
- name,
- helpText,
- hotKey,
- false,
- (toolBarConfigItemsInclude |
- (ToolBarConfigItems.Brush |
- ToolBarConfigItems.Pen |
- ToolBarConfigItems.ShapeType |
- ToolBarConfigItems.Antialiasing |
- ToolBarConfigItems.AlphaBlending)) &
- ~(toolBarConfigItemsExclude))
- {
- ForceShapeDrawType = false;
- mouseDown = false;
- _points = null;
- _cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.ShapeToolCursor.cur"));
- _cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.ShapeToolCursorMouseDown.cur"));
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose (disposing);
-
- if (!disposing) return;
- if (_cursorMouseUp != null)
- {
- _cursorMouseUp.Dispose();
- _cursorMouseUp = null;
- }
-
- if (_cursorMouseDown == null) return;
- _cursorMouseDown.Dispose();
- _cursorMouseDown = null;
- }
- }
- }