using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Text.RegularExpressions;
// This is the code for your desktop app.
// Press Ctrl+F5 (or go to Debug > Start Without Debugging) to run your app.
namespace SketchAssistant
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
fileImporter = new FileImporter(this);
}
/**********************************/
/*** CLASS VARIABLES START HERE ***/
/**********************************/
//important: add new variables only at the end of the list to keep the order of definition consistent with the order in which they are returned by GetAllVariables()
///
/// Different Program States
///
public enum ProgramState
{
Idle,
Draw,
Delete
}
///
/// Current Program State
///
private ProgramState currentState;
///
/// instance of FileImporter to handle drawing imports
///
private FileImporter fileImporter;
///
/// Dialog to select a file.
///
OpenFileDialog openFileDialog = new OpenFileDialog();
///
/// Image loaded on the left
///
private Image leftImage = null;
///
/// the graphic shown in the left window, represented as a list of polylines
///
private List leftLineList;
///
/// Image on the right
///
Image rightImage = null;
///
/// Current Line being Drawn
///
List currentLine;
///
/// All Lines in the current session
///
List> rightLineList = new List>();
///
/// Whether the Mouse is currently pressed in the rightPictureBox
///
bool mousePressed = false;
///
/// The Position of the Cursor in the right picture box
///
Point currentCursorPosition;
///
/// The Previous Cursor Position in the right picture box
///
Point previousCursorPosition;
///
/// Queue for the cursorPositions
///
Queue cursorPositions = new Queue();
///
/// The graphic representation of the right image
///
Graphics rightGraph = null;
///
/// Deletion Matrixes for checking postions of lines in the image
///
bool[,] isFilledMatrix;
HashSet[,] linesMatrix;
///
/// Size of deletion area
///
uint deletionSize = 2;
///
/// History of Actions
///
ActionHistory historyOfActions;
/******************************************/
/*** FORM SPECIFIC FUNCTIONS START HERE ***/
/******************************************/
private void Form1_Load(object sender, EventArgs e)
{
currentState = ProgramState.Idle;
this.DoubleBuffered = true;
historyOfActions = new ActionHistory(null);
UpdateButtonStatus();
}
//Resize Function connected to the form resize event, will refresh the form when it is resized
private void Form1_Resize(object sender, System.EventArgs e)
{
this.Refresh();
}
//Load button, will open an OpenFileDialog
private void loadToolStripMenuItem_Click(object sender, EventArgs e)
{
openFileDialog.Filter = "Image|*.jpg;*.png;*.jpeg";
if(openFileDialog.ShowDialog() == DialogResult.OK)
{
toolStripLoadStatus.Text = openFileDialog.SafeFileName;
leftImage = Image.FromFile(openFileDialog.FileName);
pictureBoxLeft.Image = leftImage;
//Refresh the left image box when the content is changed
this.Refresh();
}
UpdateButtonStatus();
}
///
/// Import button, will open an OpenFileDialog
///
private void examplePictureToolStripMenuItem_Click(object sender, EventArgs e)
{
openFileDialog.Filter = "Interactive Sketch-Assistant Drawing|*.isad";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
toolStripLoadStatus.Text = openFileDialog.SafeFileName;
try
{
(int, int, List) values = fileImporter.ParseISADInputFile(openFileDialog.FileName);
DrawEmptyCanvasLeft(values.Item1, values.Item2);
BindAndDrawLeftImage(values.Item3);
this.Refresh();
}
catch(FileImporterException ex)
{
ShowInfoMessage(ex.ToString());
}
}
}
//Changes the state of the program to drawing
private void drawButton_Click(object sender, EventArgs e)
{
if(rightImage != null)
{
if (currentState.Equals(ProgramState.Draw))
{
ChangeState(ProgramState.Idle);
}
else
{
ChangeState(ProgramState.Draw);
}
}
UpdateButtonStatus();
}
//Changes the state of the program to deletion
private void deleteButton_Click(object sender, EventArgs e)
{
if (rightImage != null)
{
if (currentState.Equals(ProgramState.Delete))
{
ChangeState(ProgramState.Idle);
}
else
{
ChangeState(ProgramState.Delete);
}
}
UpdateButtonStatus();
}
//Undo an action
private void undoButton_Click(object sender, EventArgs e)
{
if (historyOfActions.CanUndo())
{
HashSet affectedLines = historyOfActions.GetCurrentAction().GetLineIDs();
SketchAction.ActionType undoAction = historyOfActions.GetCurrentAction().GetActionType();
switch (undoAction)
{
case SketchAction.ActionType.Delete:
//Deleted Lines need to be shown
ChangeLines(affectedLines, true);
break;
case SketchAction.ActionType.Draw:
//Drawn lines need to be hidden
ChangeLines(affectedLines, false);
break;
default:
break;
}
}
historyOfActions.MoveAction(true);
UpdateButtonStatus();
}
//Redo an action
private void redoButton_Click(object sender, EventArgs e)
{
if (historyOfActions.CanRedo())
{
historyOfActions.MoveAction(false);
HashSet affectedLines = historyOfActions.GetCurrentAction().GetLineIDs();
SketchAction.ActionType redoAction = historyOfActions.GetCurrentAction().GetActionType();
switch (redoAction)
{
case SketchAction.ActionType.Delete:
//Deleted Lines need to be redeleted
ChangeLines(affectedLines, false);
break;
case SketchAction.ActionType.Draw:
//Drawn lines need to be redrawn
ChangeLines(affectedLines, true);
break;
default:
break;
}
}
UpdateButtonStatus();
}
//Detect Keyboard Shortcuts
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Modifiers == Keys.Control && e.KeyCode == Keys.Z)
{
undoButton_Click(sender, e);
}
if (e.Modifiers == Keys.Control && e.KeyCode == Keys.Y)
{
redoButton_Click(sender, e);
}
}
//get current Mouse positon within the right picture box
private void pictureBoxRight_MouseMove(object sender, MouseEventArgs e)
{
currentCursorPosition = ConvertCoordinates(new Point(e.X, e.Y));
}
//hold left mouse button to draw.
private void pictureBoxRight_MouseDown(object sender, MouseEventArgs e)
{
mousePressed = true;
if (currentState.Equals(ProgramState.Draw))
{
currentLine = new List();
}
}
//Lift left mouse button to stop drawing and add a new Line.
private void pictureBoxRight_MouseUp(object sender, MouseEventArgs e)
{
mousePressed = false;
if (currentState.Equals(ProgramState.Draw) && currentLine.Count > 0)
{
Line newLine = new Line(currentLine, rightLineList.Count);
rightLineList.Add(new Tuple(true, newLine));
newLine.PopulateMatrixes(isFilledMatrix, linesMatrix);
historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID()));
}
UpdateButtonStatus();
}
//Button to create a new Canvas. Will create an empty image
//which is the size of the left image, if there is one.
//If there is no image loaded the canvas will be the size of the right picture box
private void canvasButton_Click(object sender, EventArgs e)
{
if (!historyOfActions.IsEmpty())
{
if (MessageBox.Show("You have unsaved changes, creating a new canvas will discard these.",
"Attention", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == DialogResult.OK)
{
historyOfActions = new ActionHistory(lastActionTakenLabel);
DrawEmptyCanvasRight();
//The following lines cannot be in DrawEmptyCanvas()
isFilledMatrix = new bool[rightImage.Width, rightImage.Height];
linesMatrix = new HashSet[rightImage.Width, rightImage.Height];
rightLineList = new List>();
}
}
else
{
historyOfActions = new ActionHistory(lastActionTakenLabel);
DrawEmptyCanvasRight();
//The following lines cannot be in DrawEmptyCanvas()
isFilledMatrix = new bool[rightImage.Width, rightImage.Height];
linesMatrix = new HashSet[rightImage.Width, rightImage.Height];
rightLineList = new List>();
}
UpdateButtonStatus();
}
//add a Point on every tick to the Drawpath
private void mouseTimer_Tick(object sender, EventArgs e)
{
cursorPositions.Enqueue(currentCursorPosition);
previousCursorPosition = cursorPositions.Dequeue();
if (currentState.Equals(ProgramState.Draw) && mousePressed)
{
currentLine.Add(currentCursorPosition);
Line drawline = new Line(currentLine);
drawline.DrawLine(rightGraph);
pictureBoxRight.Image = rightImage;
}
if (currentState.Equals(ProgramState.Delete) && mousePressed)
{
List uncheckedPoints = Line.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition);
foreach (Point currPoint in uncheckedPoints)
{
HashSet linesToDelete = CheckDeletionMatrixesAroundPoint(currPoint, deletionSize);
if (linesToDelete.Count > 0)
{
historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Delete, linesToDelete));
foreach (int lineID in linesToDelete)
{
rightLineList[lineID] = new Tuple(false, rightLineList[lineID].Item2);
}
RepopulateDeletionMatrixes();
RedrawRightImage();
}
}
}
}
/***********************************/
/*** HELPER FUNCTIONS START HERE ***/
/***********************************/
///
/// Creates an empty Canvas
///
private void DrawEmptyCanvasRight()
{
if (leftImage == null)
{
rightImage = new Bitmap(pictureBoxRight.Width, pictureBoxRight.Height);
rightGraph = Graphics.FromImage(rightImage);
rightGraph.FillRectangle(Brushes.White, 0, 0, pictureBoxRight.Width + 10, pictureBoxRight.Height + 10);
pictureBoxRight.Image = rightImage;
}
else
{
rightImage = new Bitmap(leftImage.Width, leftImage.Height);
rightGraph = Graphics.FromImage(rightImage);
rightGraph.FillRectangle(Brushes.White, 0, 0, leftImage.Width + 10, leftImage.Height + 10);
pictureBoxRight.Image = rightImage;
}
this.Refresh();
pictureBoxRight.Refresh();
}
///
/// Creates an empty Canvas on the left
///
/// width of the new canvas in pixels
/// height of the new canvas in pixels
private void DrawEmptyCanvasLeft(int width, int height)
{
if (width == 0)
{
leftImage = new Bitmap(pictureBoxLeft.Width, pictureBoxLeft.Height);
}
else
{
leftImage = new Bitmap(width, height);
}
Graphics.FromImage(leftImage).FillRectangle(Brushes.White, 0, 0, pictureBoxLeft.Width + 10, pictureBoxLeft.Height + 10);
pictureBoxLeft.Image = leftImage;
this.Refresh();
pictureBoxLeft.Refresh();
}
///
/// Redraws all lines in lineList, for which their associated boolean value equals true.
///
private void RedrawRightImage()
{
DrawEmptyCanvasRight();
foreach (Tuple lineBoolTuple in rightLineList)
{
if (lineBoolTuple.Item1)
{
lineBoolTuple.Item2.DrawLine(rightGraph);
}
}
pictureBoxRight.Refresh();
}
///
/// Change the status of whether or not the lines are shown.
///
/// The HashSet containing the affected Line IDs.
/// True if the lines should be shown, false if they should be hidden.
private void ChangeLines(HashSet lines, bool shown)
{
foreach (int lineId in lines)
{
if (lineId <= rightLineList.Count - 1 && lineId >= 0)
{
rightLineList[lineId] = new Tuple(shown, rightLineList[lineId].Item2);
}
}
RedrawRightImage();
}
///
/// Updates the active status of buttons. Currently draw, delete, undo and redo button.
///
private void UpdateButtonStatus()
{
undoButton.Enabled = historyOfActions.CanUndo();
redoButton.Enabled = historyOfActions.CanRedo();
drawButton.Enabled = (rightImage != null);
deleteButton.Enabled = (rightImage != null);
}
///
/// A helper function which handles tasks associated witch changing states,
/// such as checking and unchecking buttons and changing the state.
///
/// The new state of the program
private void ChangeState(ProgramState newState)
{
switch (currentState)
{
case ProgramState.Draw:
drawButton.CheckState = CheckState.Unchecked;
mouseTimer.Enabled = false;
break;
case ProgramState.Delete:
deleteButton.CheckState = CheckState.Unchecked;
mouseTimer.Enabled = false;
break;
default:
break;
}
switch (newState)
{
case ProgramState.Draw:
drawButton.CheckState = CheckState.Checked;
mouseTimer.Enabled = true;
break;
case ProgramState.Delete:
deleteButton.CheckState = CheckState.Checked;
mouseTimer.Enabled = true;
break;
default:
break;
}
currentState = newState;
pictureBoxRight.Refresh();
}
///
/// A function that calculates the coordinates of a point on a zoomed in image.
///
/// The position of the mouse cursor
/// The real coordinates of the mouse cursor on the image
private Point ConvertCoordinates(Point cursorPosition)
{
Point realCoordinates = new Point(5,3);
if(pictureBoxRight.Image == null)
{
return cursorPosition;
}
int widthImage = pictureBoxRight.Image.Width;
int heightImage = pictureBoxRight.Image.Height;
int widthBox = pictureBoxRight.Width;
int heightBox = pictureBoxRight.Height;
float imageRatio = (float)widthImage / (float)heightImage;
float containerRatio = (float)widthBox / (float)heightBox;
if (imageRatio >= containerRatio)
{
//Image is wider than it is high
float zoomFactor = (float)widthImage / (float)widthBox;
float scaledHeight = heightImage / zoomFactor;
float filler = (heightBox - scaledHeight) / 2;
realCoordinates.X = (int)(cursorPosition.X * zoomFactor);
realCoordinates.Y = (int)((cursorPosition.Y - filler) * zoomFactor);
}
else
{
//Image is higher than it is wide
float zoomFactor = (float)heightImage / (float)heightBox;
float scaledWidth = widthImage / zoomFactor;
float filler = (widthBox - scaledWidth) / 2;
realCoordinates.X = (int)((cursorPosition.X - filler) * zoomFactor);
realCoordinates.Y = (int)(cursorPosition.Y * zoomFactor);
}
return realCoordinates;
}
///
/// A function that populates the matrixes needed for deletion detection with line data.
///
private void RepopulateDeletionMatrixes()
{
if(rightImage != null)
{
isFilledMatrix = new bool[rightImage.Width,rightImage.Height];
linesMatrix = new HashSet[rightImage.Width, rightImage.Height];
foreach(Tuple lineTuple in rightLineList)
{
if (lineTuple.Item1)
{
lineTuple.Item2.PopulateMatrixes(isFilledMatrix, linesMatrix);
}
}
}
}
///
/// A function that checks the deletion matrixes at a certain point
/// and returns all Line ids at that point and in a square around it in a certain range.
///
/// The point around which to check.
/// The range around the point. If range is 0, only the point is checked.
/// A List of all lines.
private HashSet CheckDeletionMatrixesAroundPoint(Point p, uint range)
{
HashSet returnSet = new HashSet();
if (p.X >= 0 && p.Y >= 0 && p.X < rightImage.Width && p.Y < rightImage.Height)
{
if (isFilledMatrix[p.X, p.Y])
{
returnSet.UnionWith(linesMatrix[p.X, p.Y]);
}
}
for (int x_mod = (int)range*(-1); x_mod < range; x_mod++)
{
for (int y_mod = (int)range * (-1); y_mod < range; y_mod++)
{
if (p.X + x_mod >= 0 && p.Y + y_mod >= 0 && p.X + x_mod < rightImage.Width && p.Y + y_mod < rightImage.Height)
{
if (isFilledMatrix[p.X + x_mod, p.Y + y_mod])
{
returnSet.UnionWith(linesMatrix[p.X + x_mod, p.Y + y_mod]);
}
}
}
}
return returnSet;
}
///
/// binds the given picture to templatePicture and draws it
///
/// the new template picture, represented as a list of polylines
///
private void BindAndDrawLeftImage(List newTemplatePicture)
{
leftLineList = newTemplatePicture;
foreach(Line l in leftLineList)
{
l.DrawLine(Graphics.FromImage(leftImage));
}
}
///
/// shows the given info message in a popup and asks the user to aknowledge it
///
/// the message to show
private void ShowInfoMessage(String message)
{
MessageBox.Show(message);
}
///
/// returns all instance variables in the order of their definition for testing
///
/// all instance variables in the order of their definition
public Object[]/*(ProgramState, FileImporter, OpenFileDialog, Image, List, Image, List, List>, bool, Point, Point, Queue, Graphics, bool[,], HashSet[,], uint, ActionHistory)*/ GetAllVariables()
{
return new Object[] { currentState, fileImporter, openFileDialog, leftImage, leftLineList, rightImage, currentLine, rightLineList, mousePressed, currentCursorPosition, previousCursorPosition, cursorPositions, rightGraph, isFilledMatrix, linesMatrix, deletionSize, historyOfActions };
}
///
/// public method wrapper for testing purposes, invoking DrawEmptyCanvas(...) and BindAndDrawLeftImage(...)
///
/// width of the parsed image
/// height of the parsed image
/// the parsed image
public void CreateCanvasAndSetPictureForTesting(int width, int height, List newImage)
{
DrawEmptyCanvasLeft(width, height);
BindAndDrawLeftImage(newImage);
}
}
}