Form1.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. using System.Text.RegularExpressions;
  11. // This is the code for your desktop app.
  12. // Press Ctrl+F5 (or go to Debug > Start Without Debugging) to run your app.
  13. namespace SketchAssistant
  14. {
  15. public partial class Form1 : Form
  16. {
  17. public Form1()
  18. {
  19. InitializeComponent();
  20. fileImporter = new FileImporter(this);
  21. }
  22. /**********************************/
  23. /*** CLASS VARIABLES START HERE ***/
  24. /**********************************/
  25. //Different Program States
  26. public enum ProgramState
  27. {
  28. Idle,
  29. Draw,
  30. Delete
  31. }
  32. //Current Program State
  33. private ProgramState currentState;
  34. /// <summary>
  35. /// instance of FileImporter to handle drawing imports
  36. /// </summary>
  37. private FileImporter fileImporter;
  38. //Dialog to select a file.
  39. OpenFileDialog openFileDialogLeft = new OpenFileDialog();
  40. /// <summary>
  41. /// Image loaded on the left
  42. /// </summary>
  43. public Image leftImage { get; private set; } = null;
  44. /// <summary>
  45. /// the graphic shown in the left window, represented as a list of polylines
  46. /// </summary>
  47. public List<Line> templatePicture { get; private set; }
  48. //Image on the right
  49. Image rightImage = null;
  50. //Current Line being Drawn
  51. List<Point> currentLine;
  52. //All Lines in the current session
  53. List<Tuple<bool,Line>> lineList = new List<Tuple<bool, Line>>();
  54. //Whether the Mouse is currently pressed in the rightPictureBox
  55. bool mousePressed = false;
  56. //The Position of the Cursor in the right picture box
  57. Point currentCursorPosition;
  58. //The Previous Cursor Position in the right picture box
  59. Point previousCursorPosition;
  60. //Queue for the cursorPositions
  61. Queue<Point> cursorPositions = new Queue<Point>();
  62. //The graphic representation of the right image
  63. Graphics graph = null;
  64. //Deletion Matrixes for checking postions of lines in the image
  65. bool[,] isFilledMatrix;
  66. HashSet<int>[,] linesMatrix;
  67. //Size of deletion area
  68. uint deletionSize = 2;
  69. //History of Actions
  70. ActionHistory historyOfActions;
  71. /******************************************/
  72. /*** FORM SPECIFIC FUNCTIONS START HERE ***/
  73. /******************************************/
  74. private void Form1_Load(object sender, EventArgs e)
  75. {
  76. currentState = ProgramState.Idle;
  77. this.DoubleBuffered = true;
  78. historyOfActions = new ActionHistory(null);
  79. UpdateButtonStatus();
  80. }
  81. //Resize Function connected to the form resize event, will refresh the form when it is resized
  82. private void Form1_Resize(object sender, System.EventArgs e)
  83. {
  84. this.Refresh();
  85. }
  86. //Load button, will open an OpenFileDialog
  87. private void loadToolStripMenuItem_Click(object sender, EventArgs e)
  88. {
  89. openFileDialogLeft.Filter = "Image|*.jpg;*.png;*.jpeg";
  90. if(openFileDialogLeft.ShowDialog() == DialogResult.OK)
  91. {
  92. toolStripLoadStatus.Text = openFileDialogLeft.SafeFileName;
  93. leftImage = Image.FromFile(openFileDialogLeft.FileName);
  94. pictureBoxLeft.Image = leftImage;
  95. //Refresh the left image box when the content is changed
  96. this.Refresh();
  97. }
  98. UpdateButtonStatus();
  99. }
  100. /// <summary>
  101. /// Import button, will open an OpenFileDialog
  102. /// </summary>
  103. private void examplePictureToolStripMenuItem_Click(object sender, EventArgs e)
  104. {
  105. openFileDialogLeft.Filter = "Interactive Sketch-Assistant Drawing|*.isad";
  106. if (openFileDialogLeft.ShowDialog() == DialogResult.OK)
  107. {
  108. toolStripLoadStatus.Text = openFileDialogLeft.SafeFileName;
  109. try
  110. {
  111. (int, int, List<Line>) values = fileImporter.ParseISADInput(openFileDialogLeft.FileName);
  112. DrawEmptyCanvasLeft(values.Item1, values.Item2);
  113. BindAndDrawLeftImage(values.Item3);
  114. this.Refresh();
  115. }
  116. catch(FileImporterException ex)
  117. {
  118. ShowInfoMessage(ex.ToString());
  119. }
  120. }
  121. }
  122. //Changes the state of the program to drawing
  123. private void drawButton_Click(object sender, EventArgs e)
  124. {
  125. if(rightImage != null)
  126. {
  127. if (currentState.Equals(ProgramState.Draw))
  128. {
  129. ChangeState(ProgramState.Idle);
  130. }
  131. else
  132. {
  133. ChangeState(ProgramState.Draw);
  134. }
  135. }
  136. UpdateButtonStatus();
  137. }
  138. //Changes the state of the program to deletion
  139. private void deleteButton_Click(object sender, EventArgs e)
  140. {
  141. if (rightImage != null)
  142. {
  143. if (currentState.Equals(ProgramState.Delete))
  144. {
  145. ChangeState(ProgramState.Idle);
  146. }
  147. else
  148. {
  149. ChangeState(ProgramState.Delete);
  150. }
  151. }
  152. UpdateButtonStatus();
  153. }
  154. //Undo an action
  155. private void undoButton_Click(object sender, EventArgs e)
  156. {
  157. if (historyOfActions.CanUndo())
  158. {
  159. HashSet<int> affectedLines = historyOfActions.GetCurrentAction().GetLineIDs();
  160. SketchAction.ActionType undoAction = historyOfActions.GetCurrentAction().GetActionType();
  161. switch (undoAction)
  162. {
  163. case SketchAction.ActionType.Delete:
  164. //Deleted Lines need to be shown
  165. ChangeLines(affectedLines, true);
  166. break;
  167. case SketchAction.ActionType.Draw:
  168. //Drawn lines need to be hidden
  169. ChangeLines(affectedLines, false);
  170. break;
  171. default:
  172. break;
  173. }
  174. }
  175. historyOfActions.MoveAction(true);
  176. UpdateButtonStatus();
  177. }
  178. //Redo an action
  179. private void redoButton_Click(object sender, EventArgs e)
  180. {
  181. if (historyOfActions.CanRedo())
  182. {
  183. historyOfActions.MoveAction(false);
  184. HashSet<int> affectedLines = historyOfActions.GetCurrentAction().GetLineIDs();
  185. SketchAction.ActionType redoAction = historyOfActions.GetCurrentAction().GetActionType();
  186. switch (redoAction)
  187. {
  188. case SketchAction.ActionType.Delete:
  189. //Deleted Lines need to be redeleted
  190. ChangeLines(affectedLines, false);
  191. break;
  192. case SketchAction.ActionType.Draw:
  193. //Drawn lines need to be redrawn
  194. ChangeLines(affectedLines, true);
  195. break;
  196. default:
  197. break;
  198. }
  199. }
  200. UpdateButtonStatus();
  201. }
  202. //Detect Keyboard Shortcuts
  203. private void Form1_KeyDown(object sender, KeyEventArgs e)
  204. {
  205. if (e.Modifiers == Keys.Control && e.KeyCode == Keys.Z)
  206. {
  207. undoButton_Click(sender, e);
  208. }
  209. if (e.Modifiers == Keys.Control && e.KeyCode == Keys.Y)
  210. {
  211. redoButton_Click(sender, e);
  212. }
  213. }
  214. //get current Mouse positon within the right picture box
  215. private void pictureBoxRight_MouseMove(object sender, MouseEventArgs e)
  216. {
  217. currentCursorPosition = ConvertCoordinates(new Point(e.X, e.Y));
  218. }
  219. //hold left mouse button to draw.
  220. private void pictureBoxRight_MouseDown(object sender, MouseEventArgs e)
  221. {
  222. mousePressed = true;
  223. if (currentState.Equals(ProgramState.Draw))
  224. {
  225. currentLine = new List<Point>();
  226. }
  227. }
  228. //Lift left mouse button to stop drawing and add a new Line.
  229. private void pictureBoxRight_MouseUp(object sender, MouseEventArgs e)
  230. {
  231. mousePressed = false;
  232. if (currentState.Equals(ProgramState.Draw) && currentLine.Count > 0)
  233. {
  234. Line newLine = new Line(currentLine, lineList.Count);
  235. lineList.Add(new Tuple<bool, Line>(true, newLine));
  236. newLine.PopulateMatrixes(isFilledMatrix, linesMatrix);
  237. historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID()));
  238. }
  239. UpdateButtonStatus();
  240. }
  241. //Button to create a new Canvas. Will create an empty image
  242. //which is the size of the left image, if there is one.
  243. //If there is no image loaded the canvas will be the size of the right picture box
  244. private void canvasButton_Click(object sender, EventArgs e)
  245. {
  246. if (!historyOfActions.IsEmpty())
  247. {
  248. if (MessageBox.Show("You have unsaved changes, creating a new canvas will discard these.",
  249. "Attention", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == DialogResult.OK)
  250. {
  251. historyOfActions = new ActionHistory(lastActionTakenLabel);
  252. DrawEmptyCanvas();
  253. //The following lines cannot be in DrawEmptyCanvas()
  254. isFilledMatrix = new bool[rightImage.Width, rightImage.Height];
  255. linesMatrix = new HashSet<int>[rightImage.Width, rightImage.Height];
  256. lineList = new List<Tuple<bool, Line>>();
  257. }
  258. }
  259. else
  260. {
  261. historyOfActions = new ActionHistory(lastActionTakenLabel);
  262. DrawEmptyCanvas();
  263. //The following lines cannot be in DrawEmptyCanvas()
  264. isFilledMatrix = new bool[rightImage.Width, rightImage.Height];
  265. linesMatrix = new HashSet<int>[rightImage.Width, rightImage.Height];
  266. lineList = new List<Tuple<bool, Line>>();
  267. }
  268. UpdateButtonStatus();
  269. }
  270. //add a Point on every tick to the Drawpath
  271. private void mouseTimer_Tick(object sender, EventArgs e)
  272. {
  273. cursorPositions.Enqueue(currentCursorPosition);
  274. previousCursorPosition = cursorPositions.Dequeue();
  275. if (currentState.Equals(ProgramState.Draw) && mousePressed)
  276. {
  277. currentLine.Add(currentCursorPosition);
  278. Line drawline = new Line(currentLine);
  279. drawline.DrawLine(graph);
  280. pictureBoxRight.Image = rightImage;
  281. }
  282. if (currentState.Equals(ProgramState.Delete) && mousePressed)
  283. {
  284. List<Point> uncheckedPoints = Line.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition);
  285. foreach (Point currPoint in uncheckedPoints)
  286. {
  287. HashSet<int> linesToDelete = CheckDeletionMatrixesAroundPoint(currPoint, deletionSize);
  288. if (linesToDelete.Count > 0)
  289. {
  290. historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Delete, linesToDelete));
  291. foreach (int lineID in linesToDelete)
  292. {
  293. lineList[lineID] = new Tuple<bool, Line>(false, lineList[lineID].Item2);
  294. }
  295. RepopulateDeletionMatrixes();
  296. RedrawRightImage();
  297. }
  298. }
  299. }
  300. }
  301. /***********************************/
  302. /*** HELPER FUNCTIONS START HERE ***/
  303. /***********************************/
  304. /// <summary>
  305. /// Creates an empty Canvas
  306. /// </summary>
  307. private void DrawEmptyCanvas()
  308. {
  309. if (leftImage == null)
  310. {
  311. rightImage = new Bitmap(pictureBoxRight.Width, pictureBoxRight.Height);
  312. graph = Graphics.FromImage(rightImage);
  313. graph.FillRectangle(Brushes.White, 0, 0, pictureBoxRight.Width + 10, pictureBoxRight.Height + 10);
  314. pictureBoxRight.Image = rightImage;
  315. }
  316. else
  317. {
  318. rightImage = new Bitmap(leftImage.Width, leftImage.Height);
  319. graph = Graphics.FromImage(rightImage);
  320. graph.FillRectangle(Brushes.White, 0, 0, leftImage.Width + 10, leftImage.Height + 10);
  321. pictureBoxRight.Image = rightImage;
  322. }
  323. this.Refresh();
  324. pictureBoxRight.Refresh();
  325. }
  326. /// <summary>
  327. /// Creates an empty Canvas on the left
  328. /// </summary>
  329. /// <param name="width"> width of the new canvas in pixels </param>
  330. /// <param name="height"> height of the new canvas in pixels </param>
  331. private void DrawEmptyCanvasLeft(int width, int height)
  332. {
  333. if (width == 0)
  334. {
  335. leftImage = new Bitmap(pictureBoxLeft.Width, pictureBoxLeft.Height);
  336. }
  337. else
  338. {
  339. leftImage = new Bitmap(width, height);
  340. }
  341. Graphics.FromImage(leftImage).FillRectangle(Brushes.White, 0, 0, pictureBoxLeft.Width + 10, pictureBoxLeft.Height + 10);
  342. pictureBoxLeft.Image = leftImage;
  343. this.Refresh();
  344. pictureBoxLeft.Refresh();
  345. }
  346. /// <summary>
  347. /// Redraws all lines in lineList, for which their associated boolean value equals true.
  348. /// </summary>
  349. private void RedrawRightImage()
  350. {
  351. DrawEmptyCanvas();
  352. foreach (Tuple<bool, Line> lineBoolTuple in lineList)
  353. {
  354. if (lineBoolTuple.Item1)
  355. {
  356. lineBoolTuple.Item2.DrawLine(graph);
  357. }
  358. }
  359. pictureBoxRight.Refresh();
  360. }
  361. /// <summary>
  362. /// Change the status of whether or not the lines are shown.
  363. /// </summary>
  364. /// <param name="lines">The HashSet containing the affected Line IDs.</param>
  365. /// <param name="shown">True if the lines should be shown, false if they should be hidden.</param>
  366. private void ChangeLines(HashSet<int> lines, bool shown)
  367. {
  368. foreach (int lineId in lines)
  369. {
  370. if (lineId <= lineList.Count - 1 && lineId >= 0)
  371. {
  372. lineList[lineId] = new Tuple<bool, Line>(shown, lineList[lineId].Item2);
  373. }
  374. }
  375. RedrawRightImage();
  376. }
  377. /// <summary>
  378. /// Updates the active status of buttons. Currently draw, delete, undo and redo button.
  379. /// </summary>
  380. private void UpdateButtonStatus()
  381. {
  382. undoButton.Enabled = historyOfActions.CanUndo();
  383. redoButton.Enabled = historyOfActions.CanRedo();
  384. drawButton.Enabled = (rightImage != null);
  385. deleteButton.Enabled = (rightImage != null);
  386. }
  387. /// <summary>
  388. /// A helper function which handles tasks associated witch changing states,
  389. /// such as checking and unchecking buttons and changing the state.
  390. /// </summary>
  391. /// <param name="newState">The new state of the program</param>
  392. private void ChangeState(ProgramState newState)
  393. {
  394. switch (currentState)
  395. {
  396. case ProgramState.Draw:
  397. drawButton.CheckState = CheckState.Unchecked;
  398. mouseTimer.Enabled = false;
  399. break;
  400. case ProgramState.Delete:
  401. deleteButton.CheckState = CheckState.Unchecked;
  402. mouseTimer.Enabled = false;
  403. break;
  404. default:
  405. break;
  406. }
  407. switch (newState)
  408. {
  409. case ProgramState.Draw:
  410. drawButton.CheckState = CheckState.Checked;
  411. mouseTimer.Enabled = true;
  412. break;
  413. case ProgramState.Delete:
  414. deleteButton.CheckState = CheckState.Checked;
  415. mouseTimer.Enabled = true;
  416. break;
  417. default:
  418. break;
  419. }
  420. currentState = newState;
  421. pictureBoxRight.Refresh();
  422. }
  423. /// <summary>
  424. /// A function that calculates the coordinates of a point on a zoomed in image.
  425. /// </summary>
  426. /// <param name="">The position of the mouse cursor</param>
  427. /// <returns>The real coordinates of the mouse cursor on the image</returns>
  428. private Point ConvertCoordinates(Point cursorPosition)
  429. {
  430. Point realCoordinates = new Point(5,3);
  431. if(pictureBoxRight.Image == null)
  432. {
  433. return cursorPosition;
  434. }
  435. int widthImage = pictureBoxRight.Image.Width;
  436. int heightImage = pictureBoxRight.Image.Height;
  437. int widthBox = pictureBoxRight.Width;
  438. int heightBox = pictureBoxRight.Height;
  439. float imageRatio = (float)widthImage / (float)heightImage;
  440. float containerRatio = (float)widthBox / (float)heightBox;
  441. if (imageRatio >= containerRatio)
  442. {
  443. //Image is wider than it is high
  444. float zoomFactor = (float)widthImage / (float)widthBox;
  445. float scaledHeight = heightImage / zoomFactor;
  446. float filler = (heightBox - scaledHeight) / 2;
  447. realCoordinates.X = (int)(cursorPosition.X * zoomFactor);
  448. realCoordinates.Y = (int)((cursorPosition.Y - filler) * zoomFactor);
  449. }
  450. else
  451. {
  452. //Image is higher than it is wide
  453. float zoomFactor = (float)heightImage / (float)heightBox;
  454. float scaledWidth = widthImage / zoomFactor;
  455. float filler = (widthBox - scaledWidth) / 2;
  456. realCoordinates.X = (int)((cursorPosition.X - filler) * zoomFactor);
  457. realCoordinates.Y = (int)(cursorPosition.Y * zoomFactor);
  458. }
  459. return realCoordinates;
  460. }
  461. /// <summary>
  462. /// A function that populates the matrixes needed for deletion detection with line data.
  463. /// </summary>
  464. private void RepopulateDeletionMatrixes()
  465. {
  466. if(rightImage != null)
  467. {
  468. isFilledMatrix = new bool[rightImage.Width,rightImage.Height];
  469. linesMatrix = new HashSet<int>[rightImage.Width, rightImage.Height];
  470. foreach(Tuple<bool,Line> lineTuple in lineList)
  471. {
  472. if (lineTuple.Item1)
  473. {
  474. lineTuple.Item2.PopulateMatrixes(isFilledMatrix, linesMatrix);
  475. }
  476. }
  477. }
  478. }
  479. /// <summary>
  480. /// A function that checks the deletion matrixes at a certain point
  481. /// and returns all Line ids at that point and in a square around it in a certain range.
  482. /// </summary>
  483. /// <param name="p">The point around which to check.</param>
  484. /// <param name="range">The range around the point. If range is 0, only the point is checked.</param>
  485. /// <returns>A List of all lines.</returns>
  486. private HashSet<int> CheckDeletionMatrixesAroundPoint(Point p, uint range)
  487. {
  488. HashSet<int> returnSet = new HashSet<int>();
  489. if (p.X >= 0 && p.Y >= 0 && p.X < rightImage.Width && p.Y < rightImage.Height)
  490. {
  491. if (isFilledMatrix[p.X, p.Y])
  492. {
  493. returnSet.UnionWith(linesMatrix[p.X, p.Y]);
  494. }
  495. }
  496. for (int x_mod = (int)range*(-1); x_mod < range; x_mod++)
  497. {
  498. for (int y_mod = (int)range * (-1); y_mod < range; y_mod++)
  499. {
  500. if (p.X + x_mod >= 0 && p.Y + y_mod >= 0 && p.X + x_mod < rightImage.Width && p.Y + y_mod < rightImage.Height)
  501. {
  502. if (isFilledMatrix[p.X + x_mod, p.Y + y_mod])
  503. {
  504. returnSet.UnionWith(linesMatrix[p.X + x_mod, p.Y + y_mod]);
  505. }
  506. }
  507. }
  508. }
  509. return returnSet;
  510. }
  511. /// <summary>
  512. /// checks that all lines of the given picture are inside the constraints of the left canvas and binds it to templatePicture
  513. /// </summary>
  514. /// <param name="newTemplatePicture"> the new template picture, represented as a list of polylines </param>
  515. /// <returns></returns>
  516. private void BindAndDrawLeftImage(List<Line> newTemplatePicture)
  517. {
  518. templatePicture = newTemplatePicture;
  519. foreach(Line l in templatePicture)
  520. {
  521. l.DrawLine(Graphics.FromImage(leftImage));
  522. }
  523. }
  524. /// <summary>
  525. /// shows the given info message in a popup and asks the user to aknowledge it
  526. /// </summary>
  527. /// <param name="message">the message to show</param>
  528. private void ShowInfoMessage(String message)
  529. {
  530. MessageBox.Show(message);
  531. }
  532. }
  533. }