ShapeTool.cs 22 KB

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