123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 |
- using PaintDotNet.Measurement.Enum;
- using PaintDotNet.Measurement.HistoryMementos;
- using PaintDotNet.Measurement.ObjInfo;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Drawing.Drawing2D;
- using System.Windows.Forms;
- namespace PaintDotNet.Measurement.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>
- public 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 = null;
- private Cursor cursorMouseUp;
- private Cursor cursorMouseDown;
- private bool shapeWasCommited = true;
- private CompoundHistoryMemento chaAlreadyOnStack = null;
- private bool useDashStyle = false; // if set to false, then the DashStyle will always be forced to DashStyle.Flat
- private bool forceShapeType = false;
- 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
- {
- return this.forceShapeType;
- }
- set
- {
- this.forceShapeType = value;
- }
- }
- protected ShapeDrawType ForcedShapeDrawType
- {
- get
- {
- return this.forcedShapeDrawType;
- }
- set
- {
- this.forcedShapeDrawType = value;
- }
- }
- protected bool UseDashStyle
- {
- get
- {
- return this.useDashStyle;
- }
- set
- {
- this.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>
- /// <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="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)
- {
- PointF lastPoint = (PointF)points[points.Count - 1];
- OnStylusUp(new StylusEventArgs(mouseButton, 0, lastPoint.X, lastPoint.Y, 0));
- }
- if (!this.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 (!this.shapeWasCommited)
- {
- CommitShape();
- }
- this.ClearSavedMemory();
- this.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
- this.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)
- {
- SizeF delta = new SizeF(e.Fx - lastXY.X, e.Fy - lastXY.Y);
- for (int i = 0; i < points.Count; ++i)
- {
- PointF ptF = (PointF)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))
- {
- PointF mouseXY = new PointF(e.Fx, e.Fy);
- points.Add(mouseXY);
- }
- }
- public virtual PixelOffsetMode GetPixelOffsetMode()
- {
- return PixelOffsetMode.Half;
- }
- protected List<PointF> GetTrimmedShapePath()
- {
- List<PointF> pointsCopy = new List<PointF>(this.points);
- pointsCopy = TrimShapePath(pointsCopy);
- return pointsCopy;
- }
- protected void SetShapePath(List<PointF> newPoints)
- {
- this.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 (!this.useDashStyle)
- {
- outlinePen.DashStyle = DashStyle.Solid;
- }
- 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
- if (AppEnvironment.AntiAliasing())
- {
- renderArgs.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
- }
- else
- {
- renderArgs.Graphics.SmoothingMode = SmoothingMode.None;
- }
- // also set the pixel offset mode
- renderArgs.Graphics.PixelOffsetMode = GetPixelOffsetMode();
- // figure out how we're going to draw
- ShapeDrawType drawType;
- if (ForceShapeDrawType)
- {
- drawType = ForcedShapeDrawType;
- }
- else
- {
- drawType = AppEnvironment.ShapeDrawType();
- }
- // get the region we want to save
- points = this.TrimShapePath(points);
- PointF[] pointsArray = points.ToArray();
- PdnGraphicsPath shapePath = CreateShapePath(pointsArray);
- if (shapePath != null)
- {
- // create non-optimized interior region
- PdnRegion interiorRegion = new PdnRegion(shapePath);
- // create non-optimized outline region
- PdnRegion outlineRegion;
- using (PdnGraphicsPath outlinePath = (PdnGraphicsPath)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)
- {
- 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);
- PdnRegion invalidRegion = new PdnRegion();
- invalidRegion.MakeEmpty();
- // set up alpha blending
- renderArgs.Graphics.CompositingMode = AppEnvironment.GetCompositingMode();
- SaveRegion(invalidOutlineRegion, invalidOutlineRegion.GetBoundsInt());
- this.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());
- this.interiorSaveRegion = invalidInteriorRegion;
- 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;
- }
- outlinePen.Dispose();
- 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;
- ArrayList has = new ArrayList();
- PdnRegion activeRegion = Selection.CreateRegion();
- if (outlineSaveRegion != null)
- {
- using (PdnRegion clipTest = activeRegion.Clone())
- {
- clipTest.Intersect(outlineSaveRegion);
- if (!clipTest.IsEmpty())
- {
- BitmapHistoryMemento bha = new BitmapHistoryMemento(Name, Image, this.DocumentWorkspace,
- ActiveLayerIndex, outlineSaveRegion, this.ScratchSurface);
- has.Add(bha);
- outlineSaveRegion.Dispose();
- outlineSaveRegion = null;
- }
- }
- }
- if (interiorSaveRegion != null)
- {
- using (PdnRegion clipTest = activeRegion.Clone())
- {
- clipTest.Intersect(interiorSaveRegion);
- if (!clipTest.IsEmpty())
- {
- BitmapHistoryMemento bha = new BitmapHistoryMemento(Name, Image, this.DocumentWorkspace,
- ActiveLayerIndex, interiorSaveRegion, this.ScratchSurface);
- has.Add(bha);
- interiorSaveRegion.Dispose();
- interiorSaveRegion = null;
- }
- }
- }
- if (has.Count > 0)
- {
- CompoundHistoryMemento cha = new CompoundHistoryMemento(Name, Image, (HistoryMemento[])has.ToArray(typeof(HistoryMemento)));
- if (this.chaAlreadyOnStack == null)
- {
- //HistoryStack.PushNewMemento(cha);
- }
- else
- {
- this.chaAlreadyOnStack.PushNewAction(cha);
- this.chaAlreadyOnStack = null;
- }
- }
- activeRegion.Dispose();
- points = null;
- Update();
- this.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
- CompoundHistoryMemento cha = new CompoundHistoryMemento(Name, Image, new List<HistoryMemento>());
- //HistoryStack.PushNewMemento(cha);
- this.chaAlreadyOnStack = cha;
- }
- }
- }
- public ShapeTool(IDocumentWorkspace documentWorkspace,
- ImageResource toolBarImage,
- string name,
- string helpText)
- : this(documentWorkspace,
- toolBarImage,
- name,
- helpText,
- defaultShortcut,
- ToolBarConfigItems.None,
- ToolBarConfigItems.None)
- {
- }
- public ShapeTool(IDocumentWorkspace documentWorkspace,
- ImageResource toolBarImage,
- string name,
- string helpText,
- ToolBarConfigItems toolBarConfigItemsInclude,
- ToolBarConfigItems toolBarConfigItemsExclude)
- : this(documentWorkspace,
- toolBarImage,
- name,
- helpText,
- defaultShortcut,
- toolBarConfigItemsInclude,
- toolBarConfigItemsExclude)
- {
- }
- public ShapeTool(IDocumentWorkspace 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))
- {
- this.mouseDown = false;
- this.points = null;
- this.cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.ShapeToolCursor.cur"));
- this.cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.ShapeToolCursorMouseDown.cur"));
- }
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (disposing)
- {
- if (cursorMouseUp != null)
- {
- cursorMouseUp.Dispose();
- cursorMouseUp = null;
- }
- if (cursorMouseDown != null)
- {
- cursorMouseDown.Dispose();
- cursorMouseDown = null;
- }
- }
- }
- }
- }
|