PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/src/tools/ShapeTool.cs

https://bitbucket.org/tuldok89/openpdn
C# | 651 lines | 478 code | 105 blank | 68 comment | 81 complexity | 1cc84b4bf7f7340ae416a2790c684c78 MD5 | raw file
  1. /////////////////////////////////////////////////////////////////////////////////
  2. // Paint.NET //
  3. // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
  4. // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
  5. // See src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using PaintDotNet.Base;
  10. using PaintDotNet.HistoryMementos;
  11. using System;
  12. using System.Collections;
  13. using System.Collections.Generic;
  14. using System.Drawing;
  15. using System.Drawing.Drawing2D;
  16. using System.Windows.Forms;
  17. namespace PaintDotNet.Tools
  18. {
  19. /// <summary>
  20. /// Allows the user to draw a shape that can be defined using two points on the canvas.
  21. /// The user clicks and drags between two points to define the area that bounds the shape.
  22. /// </summary>
  23. internal abstract class ShapeTool
  24. : Tool
  25. {
  26. private const char DefaultShortcut = 'o';
  27. private bool _moveOriginMode;
  28. private PointF _lastXY;
  29. private bool mouseDown;
  30. private MouseButtons _mouseButton;
  31. private BitmapLayer _bitmapLayer;
  32. private RenderArgs _renderArgs;
  33. private PdnRegion _interiorSaveRegion;
  34. private PdnRegion _outlineSaveRegion;
  35. private List<PointF> _points;
  36. private PdnRegion _lastDrawnRegion;
  37. private Cursor _cursorMouseUp;
  38. private Cursor _cursorMouseDown;
  39. private bool _shapeWasCommited = true;
  40. private CompoundHistoryMemento _chaAlreadyOnStack;
  41. private bool _useDashStyle; // if set to false, then the DashStyle will always be forced to DashStyle.Flat
  42. private ShapeDrawType _forcedShapeDrawType = ShapeDrawType.Both;
  43. protected override bool SupportsInk
  44. {
  45. get
  46. {
  47. return true;
  48. }
  49. }
  50. // This is for shapes that should only be draw in one ShapeDrawType
  51. // The line shape, for instance, should only ever be drawn in ShapeDrawType.Outline
  52. protected bool ForceShapeDrawType { get; set; }
  53. protected ShapeDrawType ForcedShapeDrawType
  54. {
  55. get
  56. {
  57. return _forcedShapeDrawType;
  58. }
  59. set
  60. {
  61. _forcedShapeDrawType = value;
  62. }
  63. }
  64. protected bool UseDashStyle
  65. {
  66. get
  67. {
  68. return _useDashStyle;
  69. }
  70. set
  71. {
  72. _useDashStyle = value;
  73. }
  74. }
  75. /// <summary>
  76. /// Different shapes may not require all the points given to them, and as such
  77. /// if the user is drawing for a long time there may be lots of memory that's
  78. /// allocated that doesn't need to be. So before CreateShapePath is called,
  79. /// this method is called first.
  80. /// For example, the LineTool would return a new array containing only the
  81. /// first and last points.
  82. /// It is ok to return the same array that was passed in, even if it is modified.
  83. /// </summary>
  84. /// <param name="points">A list containing PointF instances.</param>
  85. /// <param name="trimThesePoints"></param>
  86. /// <returns></returns>
  87. protected virtual List<PointF> TrimShapePath(List<PointF> trimThesePoints)
  88. {
  89. return trimThesePoints;
  90. }
  91. /// <summary>
  92. /// Override this function to return an "optimized" region that encompasses
  93. /// the shape's outline. For example, a circle would return a list of rectangles
  94. /// that traces the outline. This is necessary because normally simplification
  95. /// will produce a region that, for a circle's outline, encompasses its
  96. /// interior as well. If you return null, then the default simplification
  97. /// algorithm will be used.
  98. /// </summary>
  99. /// <param name="points"></param>
  100. /// <param name="optimizeThesePoints"></param>
  101. /// <param name="path"></param>
  102. /// <returns></returns>
  103. protected virtual RectangleF[] GetOptimizedShapeOutlineRegion(PointF[] optimizeThesePoints, PdnGraphicsPath path)
  104. {
  105. return null;
  106. }
  107. // Implement this!
  108. protected abstract PdnGraphicsPath CreateShapePath(PointF[] shapePoints);
  109. protected override void OnActivate()
  110. {
  111. base.OnActivate();
  112. _outlineSaveRegion = null;
  113. _interiorSaveRegion = null;
  114. // creates a bitmap layer from the active layer
  115. _bitmapLayer = (BitmapLayer)ActiveLayer;
  116. // create Graphics object
  117. _renderArgs = new RenderArgs(_bitmapLayer.Surface);
  118. _lastDrawnRegion = new PdnRegion();
  119. _lastDrawnRegion.MakeEmpty();
  120. }
  121. protected override void OnDeactivate()
  122. {
  123. base.OnDeactivate();
  124. if (mouseDown)
  125. {
  126. var lastPoint = _points[_points.Count - 1];
  127. OnStylusUp(new StylusEventArgs(_mouseButton, 0, lastPoint.X, lastPoint.Y, 0));
  128. }
  129. if (!_shapeWasCommited)
  130. {
  131. CommitShape();
  132. }
  133. _bitmapLayer = null;
  134. if (_renderArgs != null)
  135. {
  136. _renderArgs.Dispose();
  137. _renderArgs = null;
  138. }
  139. if (_outlineSaveRegion != null)
  140. {
  141. _outlineSaveRegion.Dispose();
  142. _outlineSaveRegion = null;
  143. }
  144. if (_interiorSaveRegion != null)
  145. {
  146. _interiorSaveRegion.Dispose();
  147. _interiorSaveRegion = null;
  148. }
  149. _points = null;
  150. }
  151. protected virtual void OnShapeBegin()
  152. {
  153. }
  154. /// <summary>
  155. /// Called when the shape is finished being traced by the default input handlers.
  156. /// </summary>
  157. /// <remarks>Do not call the base implementation of this method if you are overriding it.</remarks>
  158. /// <returns>true to commit the shape immediately</returns>
  159. protected virtual bool OnShapeEnd()
  160. {
  161. return true;
  162. }
  163. protected override void OnStylusDown(StylusEventArgs e)
  164. {
  165. base.OnStylusDown(e);
  166. if (!_shapeWasCommited)
  167. {
  168. CommitShape();
  169. }
  170. ClearSavedMemory();
  171. ClearSavedRegion();
  172. _cursorMouseUp = Cursor;
  173. Cursor = _cursorMouseDown;
  174. if (mouseDown && e.Button == _mouseButton)
  175. {
  176. return;
  177. }
  178. if (mouseDown)
  179. {
  180. _moveOriginMode = true;
  181. _lastXY = new PointF(e.Fx, e.Fy);
  182. OnStylusMove(e);
  183. }
  184. else if (((e.Button & MouseButtons.Left) == MouseButtons.Left) ||
  185. ((e.Button & MouseButtons.Right) == MouseButtons.Right))
  186. {
  187. // begin new shape
  188. _shapeWasCommited = false;
  189. OnShapeBegin();
  190. mouseDown = true;
  191. _mouseButton = e.Button;
  192. using (PdnRegion clipRegion = Selection.CreateRegion())
  193. {
  194. _renderArgs.Graphics.SetClip(clipRegion.GetRegionReadOnly(), CombineMode.Replace);
  195. }
  196. // reset the points we're drawing!
  197. _points = new List<PointF>();
  198. OnStylusMove(e);
  199. }
  200. }
  201. protected override void OnStylusMove(StylusEventArgs e)
  202. {
  203. base.OnStylusMove (e);
  204. if (_moveOriginMode)
  205. {
  206. var delta = new SizeF(e.Fx - _lastXY.X, e.Fy - _lastXY.Y);
  207. for (int i = 0; i < _points.Count; ++i)
  208. {
  209. var ptF = _points[i];
  210. ptF.X += delta.Width;
  211. ptF.Y += delta.Height;
  212. _points[i] = ptF;
  213. }
  214. _lastXY = new PointF(e.Fx, e.Fy);
  215. }
  216. else if (mouseDown && ((e.Button & _mouseButton) != MouseButtons.None))
  217. {
  218. var mouseXY = new PointF(e.Fx, e.Fy);
  219. _points.Add(mouseXY);
  220. }
  221. }
  222. public virtual PixelOffsetMode GetPixelOffsetMode()
  223. {
  224. return PixelOffsetMode.Half;
  225. }
  226. protected List<PointF> GetTrimmedShapePath()
  227. {
  228. var pointsCopy = new List<PointF>(_points);
  229. pointsCopy = TrimShapePath(pointsCopy);
  230. return pointsCopy;
  231. }
  232. protected void SetShapePath(List<PointF> newPoints)
  233. {
  234. _points = newPoints;
  235. }
  236. protected void RenderShape()
  237. {
  238. // create the Pen we will use to draw with
  239. Pen outlinePen = null;
  240. Brush interiorBrush = null;
  241. PenInfo pi = AppEnvironment.PenInfo;
  242. BrushInfo bi = AppEnvironment.BrushInfo;
  243. ColorBgra primary = AppEnvironment.PrimaryColor;
  244. ColorBgra secondary = AppEnvironment.SecondaryColor;
  245. if (!ForceShapeDrawType && AppEnvironment.ShapeDrawType == ShapeDrawType.Interior)
  246. {
  247. Utility.Swap(ref primary, ref secondary);
  248. }
  249. // Initialize pens and brushes to the correct colors
  250. if ((_mouseButton & MouseButtons.Left) == MouseButtons.Left)
  251. {
  252. outlinePen = pi.CreatePen(AppEnvironment.BrushInfo, primary.ToColor(), secondary.ToColor());
  253. interiorBrush = bi.CreateBrush(secondary.ToColor(), primary.ToColor());
  254. }
  255. else if ((_mouseButton & MouseButtons.Right) == MouseButtons.Right)
  256. {
  257. outlinePen = pi.CreatePen(AppEnvironment.BrushInfo, secondary.ToColor(), primary.ToColor());
  258. interiorBrush = bi.CreateBrush(primary.ToColor(), secondary.ToColor());
  259. }
  260. if (!_useDashStyle)
  261. {
  262. if (outlinePen != null) outlinePen.DashStyle = DashStyle.Solid;
  263. }
  264. if (outlinePen != null)
  265. {
  266. outlinePen.LineJoin = LineJoin.MiterClipped;
  267. outlinePen.MiterLimit = 2;
  268. }
  269. // redraw the old saveSurface
  270. if (_interiorSaveRegion != null)
  271. {
  272. RestoreRegion(_interiorSaveRegion);
  273. _interiorSaveRegion.Dispose();
  274. _interiorSaveRegion = null;
  275. }
  276. if (_outlineSaveRegion != null)
  277. {
  278. RestoreRegion(_outlineSaveRegion);
  279. _outlineSaveRegion.Dispose();
  280. _outlineSaveRegion = null;
  281. }
  282. // anti-aliasing? Don't mind if I do
  283. _renderArgs.Graphics.SmoothingMode = AppEnvironment.AntiAliasing ? SmoothingMode.AntiAlias : SmoothingMode.None;
  284. // also set the pixel offset mode
  285. _renderArgs.Graphics.PixelOffsetMode = GetPixelOffsetMode();
  286. // figure out how we're going to draw
  287. ShapeDrawType drawType = ForceShapeDrawType ? ForcedShapeDrawType : AppEnvironment.ShapeDrawType;
  288. // get the region we want to save
  289. _points = TrimShapePath(_points);
  290. PointF[] pointsArray = _points.ToArray();
  291. PdnGraphicsPath shapePath = CreateShapePath(pointsArray);
  292. if (shapePath != null)
  293. {
  294. // create non-optimized interior region
  295. var interiorRegion = new PdnRegion(shapePath);
  296. // create non-optimized outline region
  297. PdnRegion outlineRegion;
  298. using (var outlinePath = shapePath.Clone())
  299. {
  300. try
  301. {
  302. outlinePath.Widen(outlinePen);
  303. outlineRegion = new PdnRegion(outlinePath);
  304. }
  305. // Sometimes GDI+ gets cranky if we have a very small shape (e.g. all points
  306. // are coincident).
  307. catch (OutOfMemoryException)
  308. {
  309. outlineRegion = new PdnRegion(shapePath);
  310. }
  311. }
  312. // create optimized outlineRegion for purposes of rendering, if it is possible to do so
  313. // shapes will often provide an "optimized" region that circumvents the fact that
  314. // we'd otherwise get a region that encompasses the outline *and* the interior, thus
  315. // slowing rendering significantly in many cases.
  316. RectangleF[] optimizedOutlineRegion = GetOptimizedShapeOutlineRegion(pointsArray, shapePath);
  317. PdnRegion invalidOutlineRegion;
  318. if (optimizedOutlineRegion != null)
  319. {
  320. if (outlinePen != null)
  321. Utility.InflateRectanglesInPlace(optimizedOutlineRegion, (int)(outlinePen.Width + 2));
  322. invalidOutlineRegion = Utility.RectanglesToRegion(optimizedOutlineRegion);
  323. }
  324. else
  325. {
  326. invalidOutlineRegion = Utility.SimplifyAndInflateRegion(outlineRegion, Utility.DefaultSimplificationFactor, (int)(outlinePen.Width + 2));
  327. }
  328. // create optimized interior region
  329. PdnRegion invalidInteriorRegion = Utility.SimplifyAndInflateRegion(interiorRegion, Utility.DefaultSimplificationFactor, 3);
  330. var invalidRegion = new PdnRegion();
  331. invalidRegion.MakeEmpty();
  332. // set up alpha blending
  333. _renderArgs.Graphics.CompositingMode = AppEnvironment.GetCompositingMode();
  334. SaveRegion(invalidOutlineRegion, invalidOutlineRegion.GetBoundsInt());
  335. _outlineSaveRegion = invalidOutlineRegion;
  336. if ((drawType & ShapeDrawType.Outline) != 0)
  337. {
  338. shapePath.Draw(_renderArgs.Graphics, outlinePen);
  339. }
  340. invalidRegion.Union(invalidOutlineRegion);
  341. // draw shape
  342. if ((drawType & ShapeDrawType.Interior) != 0)
  343. {
  344. SaveRegion(invalidInteriorRegion, invalidInteriorRegion.GetBoundsInt());
  345. _interiorSaveRegion = invalidInteriorRegion;
  346. if (interiorBrush != null) _renderArgs.Graphics.FillPath(interiorBrush, shapePath);
  347. invalidRegion.Union(invalidInteriorRegion);
  348. }
  349. else
  350. {
  351. invalidInteriorRegion.Dispose();
  352. invalidInteriorRegion = null;
  353. }
  354. _bitmapLayer.Invalidate(invalidRegion);
  355. invalidRegion.Dispose();
  356. invalidRegion = null;
  357. outlineRegion.Dispose();
  358. outlineRegion = null;
  359. interiorRegion.Dispose();
  360. interiorRegion = null;
  361. }
  362. Update();
  363. if (shapePath != null)
  364. {
  365. shapePath.Dispose();
  366. shapePath = null;
  367. }
  368. if (outlinePen != null) outlinePen.Dispose();
  369. if (interiorBrush != null) interiorBrush.Dispose();
  370. }
  371. protected override void OnMouseMove(MouseEventArgs e)
  372. {
  373. base.OnMouseMove(e);
  374. // if mouse button not down then leave function
  375. if (mouseDown && ((e.Button & _mouseButton) != MouseButtons.None))
  376. {
  377. RenderShape();
  378. }
  379. }
  380. protected override void OnKeyDown(KeyEventArgs e)
  381. {
  382. if (mouseDown)
  383. {
  384. RenderShape();
  385. }
  386. base.OnKeyDown(e);
  387. }
  388. protected override void OnKeyUp(KeyEventArgs e)
  389. {
  390. if (mouseDown)
  391. {
  392. RenderShape();
  393. }
  394. base.OnKeyUp(e);
  395. }
  396. protected virtual void OnShapeCommitting()
  397. {
  398. }
  399. protected void CommitShape()
  400. {
  401. OnShapeCommitting();
  402. mouseDown = false;
  403. var has = new ArrayList();
  404. PdnRegion activeRegion = Selection.CreateRegion();
  405. if (_outlineSaveRegion != null)
  406. {
  407. using (PdnRegion clipTest = activeRegion.Clone())
  408. {
  409. clipTest.Intersect(_outlineSaveRegion);
  410. if (!clipTest.IsEmpty())
  411. {
  412. var bha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace,
  413. ActiveLayerIndex, _outlineSaveRegion, ScratchSurface);
  414. has.Add(bha);
  415. _outlineSaveRegion.Dispose();
  416. _outlineSaveRegion = null;
  417. }
  418. }
  419. }
  420. if (_interiorSaveRegion != null)
  421. {
  422. using (PdnRegion clipTest = activeRegion.Clone())
  423. {
  424. clipTest.Intersect(_interiorSaveRegion);
  425. if (!clipTest.IsEmpty())
  426. {
  427. var bha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace,
  428. ActiveLayerIndex, _interiorSaveRegion, ScratchSurface);
  429. has.Add(bha);
  430. _interiorSaveRegion.Dispose();
  431. _interiorSaveRegion = null;
  432. }
  433. }
  434. }
  435. if (has.Count > 0)
  436. {
  437. var cha = new CompoundHistoryMemento(Name, Image, (HistoryMemento[])has.ToArray(typeof(HistoryMemento)));
  438. if (_chaAlreadyOnStack == null)
  439. {
  440. HistoryStack.PushNewMemento(cha);
  441. }
  442. else
  443. {
  444. _chaAlreadyOnStack.PushNewAction(cha);
  445. _chaAlreadyOnStack = null;
  446. }
  447. }
  448. activeRegion.Dispose();
  449. _points = null;
  450. Update();
  451. _shapeWasCommited = true;
  452. }
  453. protected override void OnStylusUp(StylusEventArgs e)
  454. {
  455. base.OnStylusUp(e);
  456. Cursor = _cursorMouseUp;
  457. if (_moveOriginMode)
  458. {
  459. _moveOriginMode = false;
  460. }
  461. else if (mouseDown)
  462. {
  463. bool doCommit = OnShapeEnd();
  464. if (doCommit)
  465. {
  466. CommitShape();
  467. }
  468. else
  469. {
  470. // place a 'sentinel' history action on the stack that will be filled in later
  471. var cha = new CompoundHistoryMemento(Name, Image, new List<HistoryMemento>());
  472. HistoryStack.PushNewMemento(cha);
  473. _chaAlreadyOnStack = cha;
  474. }
  475. }
  476. }
  477. protected ShapeTool(DocumentWorkspace documentWorkspace,
  478. ImageResource toolBarImage,
  479. string name,
  480. string helpText)
  481. : this(documentWorkspace,
  482. toolBarImage,
  483. name,
  484. helpText,
  485. DefaultShortcut,
  486. ToolBarConfigItems.None,
  487. ToolBarConfigItems.None)
  488. {
  489. }
  490. protected ShapeTool(DocumentWorkspace documentWorkspace,
  491. ImageResource toolBarImage,
  492. string name,
  493. string helpText,
  494. ToolBarConfigItems toolBarConfigItemsInclude,
  495. ToolBarConfigItems toolBarConfigItemsExclude)
  496. : this(documentWorkspace,
  497. toolBarImage,
  498. name,
  499. helpText,
  500. DefaultShortcut,
  501. toolBarConfigItemsInclude,
  502. toolBarConfigItemsExclude)
  503. {
  504. }
  505. protected ShapeTool(DocumentWorkspace documentWorkspace,
  506. ImageResource toolBarImage,
  507. string name,
  508. string helpText,
  509. char hotKey,
  510. ToolBarConfigItems toolBarConfigItemsInclude,
  511. ToolBarConfigItems toolBarConfigItemsExclude)
  512. : base(documentWorkspace,
  513. toolBarImage,
  514. name,
  515. helpText,
  516. hotKey,
  517. false,
  518. (toolBarConfigItemsInclude |
  519. (ToolBarConfigItems.Brush |
  520. ToolBarConfigItems.Pen |
  521. ToolBarConfigItems.ShapeType |
  522. ToolBarConfigItems.Antialiasing |
  523. ToolBarConfigItems.AlphaBlending)) &
  524. ~(toolBarConfigItemsExclude))
  525. {
  526. ForceShapeDrawType = false;
  527. mouseDown = false;
  528. _points = null;
  529. _cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.ShapeToolCursor.cur"));
  530. _cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.ShapeToolCursorMouseDown.cur"));
  531. }
  532. protected override void Dispose(bool disposing)
  533. {
  534. base.Dispose (disposing);
  535. if (!disposing) return;
  536. if (_cursorMouseUp != null)
  537. {
  538. _cursorMouseUp.Dispose();
  539. _cursorMouseUp = null;
  540. }
  541. if (_cursorMouseDown == null) return;
  542. _cursorMouseDown.Dispose();
  543. _cursorMouseDown = null;
  544. }
  545. }
  546. }