BaseTool.h

#pragma once
#include<vector>
#include<map>
#include <string>

enum class StartStop 
{
	Start,
	Stop
};

enum class ToolsType 
{
	Polygon,
	Line,
	Fixed
};
enum class ToolState {
	Active,
	Completed,
	FinalPolygon	
};

class BaseTool
{
public:
	~BaseTool();
	virtual void draw(Gdiplus::Graphics& drawerGraphics, const CRect& canvasRect, int frameNumber)=0;
	virtual void reset(int frameNumber)=0;
	virtual void setHighQualityDrawingGraphicsProperty(Gdiplus::Graphics& drawerGraphics)=0;
	void SetPolygonPoints(const std::vector<Gdiplus::Point>& contourPoints, int frameNumber);	
	virtual void setPoints(const CPoint& startPoint, int frameNumber, bool mouseTrace, StartStop StartStoptype = StartStop::Start) = 0;
	virtual ToolState getToolState(int frameNumber, ToolsType toolType = ToolsType::Polygon) = 0;
	virtual void setToolState(int frameNumber, ToolState newState, ToolsType toolType = ToolsType::Polygon)=0;
	Gdiplus::Point getStartPoint(int frameNumber);
	virtual bool getArea(double& area, int frameNumber) = 0;
	virtual bool getLength(double& length, int frameNumber) = 0;
	
protected:	

	Gdiplus::Color m_Color;
	double m_area = 0.0;    
	double m_length = 0.0;  
public:

	struct PolygonParams
	{
		ToolState toolState;
		std::vector<std::pair<Gdiplus::Point, Gdiplus::Point>> PolygonToolStartEndPoint;
	};
	std::map<int, PolygonParams> m_polygonParams_map;
};


polygonTool.cpp

#include "pch.h"
#include "PolygonTool.h"
#include "pch.h"
#include <sstream>
#include <iomanip>

const Gdiplus::Color MM_STRING_COLOR = Gdiplus::Color::White;
const PWCHAR MM_STRING_FONT_FACE = L"Arial";
const int MM_STRING_FONT_SIZE = 18;
const Gdiplus::REAL POLYGON_DRAWING_PEN_WIDTH_IN_PIXEL = 2;
const int POLYGON_TOOL_CROSS_LINE_LENGTH = 5;
const Gdiplus::Color CROSS_DRAWING_PEN_COLOR = Gdiplus::Color::White;
const Gdiplus::REAL CROSS_DRAWING_PEN_WIDTH_IN_PIXEL = 2;

PolygonTool::PolygonTool(Gdiplus::Color lineColor)
{
	m_Color = lineColor;
}

PolygonTool::~PolygonTool()
{
}

void PolygonTool::setStartPoint(const CPoint& startPoint,int frameNumber, bool mouseTrace)
{
	std::pair<Gdiplus::Point, Gdiplus::Point> point;
	if (mouseTrace)
	{
		point.first = Gdiplus::Point(startPoint.x, startPoint.y);
		point.second = Gdiplus::Point(startPoint.x, startPoint.y);
	}
	else
	{
		point.first = Gdiplus::Point(startPoint.x, startPoint.y);
	}
	m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.emplace_back(point);
	m_polygonParams_map[frameNumber].toolState=ToolState::Active;
	setCrossLinePoints(true, startPoint, frameNumber);
}

void PolygonTool::setEndPoint(const CPoint& startPoint,int frameNumber, bool mouseTrace)
{
	if (mouseTrace)
	{
		std::pair<Gdiplus::Point, Gdiplus::Point> point;
		point.first = Gdiplus::Point(startPoint.x, startPoint.y);
		point.second = Gdiplus::Point(startPoint.x, startPoint.y);
		setCrossLinePoints(false, startPoint, frameNumber);
		m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.emplace_back(point);
	}
	else
	{
		std::pair<Gdiplus::Point, Gdiplus::Point> point;
		point = m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.at(m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.size() - 1);
		point.second = Gdiplus::Point(startPoint.x, startPoint.y);
		auto& startPoint = m_polygonParams_map[frameNumber].PolygonToolStartEndPoint[0];
		setCrossLinePoints(true, CPoint(startPoint.first.X, startPoint.first.Y), frameNumber);
		setCrossLinePoints(false, CPoint(point.second.X, point.second.Y), frameNumber);
		m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.at(m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.size() - 1) = point;
	}
	//if (isToolComplete)
	//{
	//	m_polygonParams_map[frameNumber].toolState = ToolState::Completed;
	//}
}

bool PolygonTool::getArea(double& area, int frameNumber)
{
	area = m_area;
	return true;
}

bool PolygonTool::getLength(double& length, int frameNumber)
{
	length = m_length;
	return true;
}

void PolygonTool::setPoints(const CPoint& startPoint, int frameNumber, bool mouseTrace, StartStop StartStoptype)
{
	if (StartStoptype == StartStop::Start) {
		setStartPoint(startPoint, frameNumber, true);
	}
	if (StartStoptype == StartStop::Stop) {
		setEndPoint(startPoint,frameNumber, true);
	}
}

ToolState PolygonTool::getToolState(int frameNumber, ToolsType toolType)
{
	return m_polygonParams_map[frameNumber].toolState;
}

void PolygonTool::setToolState(int frameNumber, ToolState newState, ToolsType toolType)
{
	m_polygonParams_map[frameNumber].toolState = newState;
}


void PolygonTool::drawFinal(Gdiplus::Graphics& drawerGraphics, const CRect& canvasRect, int frameNumber)
{
	Gdiplus::Pen polygonDrawingPen(Gdiplus::Color::DarkOrange, POLYGON_DRAWING_PEN_WIDTH_IN_PIXEL);
	polygonDrawingPen.SetDashStyle(Gdiplus::DashStyle::DashStyleDash);
	std::vector< Gdiplus::Point> points;
	for (int i = 0; i < m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.size(); i++)
		points.emplace_back(m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.at(i).first);
	if (points.size() > 2)
	{
		drawerGraphics.DrawPolygon(&polygonDrawingPen, &points.at(0), (int)points.size());
		calculateAreaAndLength(points, drawerGraphics, canvasRect);
	}
}

void PolygonTool::draw(Gdiplus::Graphics& drawerGraphics, const CRect& canvasRect, int frameNumber)
{	
	ToolState currentState = getToolState(frameNumber);
	if (currentState == ToolState::FinalPolygon)
	{
		drawFinal(drawerGraphics, canvasRect, frameNumber);
	}
	else{
		Draw(drawerGraphics, frameNumber);
		drawPolygonEndPointCross(drawerGraphics, frameNumber);
	}
}

void PolygonTool::reset(int frameNumber)
{
	setStartPoint(CPoint(0, 0),frameNumber);
	setEndPoint(CPoint(0, 0), false, frameNumber);
	m_polygonParams_map[frameNumber].toolState=ToolState::Completed;
	m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.clear();
}

void PolygonTool::setHighQualityDrawingGraphicsProperty(Gdiplus::Graphics& drawerGraphics)
{
	drawerGraphics.SetTextContrast(100);
	drawerGraphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit);
	drawerGraphics.SetCompositingQuality(Gdiplus::CompositingQualityHighQuality);
	drawerGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
	drawerGraphics.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBicubic);
	drawerGraphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
}

void PolygonTool::Draw(Gdiplus::Graphics& lineToolDrawerGraphics, int frameNumber)
{
	Gdiplus::GraphicsPath path;
	Gdiplus::Pen polygonDrawingPen(m_Color, POLYGON_DRAWING_PEN_WIDTH_IN_PIXEL);
	polygonDrawingPen.SetDashStyle(Gdiplus::DashStyle::DashStyleDash);
	for (int i = 0; i < m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.size(); i++)
		path.AddLine(m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.at(i).first, m_polygonParams_map[frameNumber].PolygonToolStartEndPoint.at(i).second);
	lineToolDrawerGraphics.DrawPath(&polygonDrawingPen, &path);
}

void PolygonTool::calculateAreaAndLength(std::vector< Gdiplus::Point> points, Gdiplus::Graphics& drawerGraphics, const CRect& canvasRect)
{
	double area = 0.0;
	double length = 0.0;
	auto n = points.size();
	auto j = n - 1;
	Gdiplus::Point stringRightPoint(0,0);
	Gdiplus::Point stringLeftPoint(points[0]);
	for (int i = 0; i < n; i++)
	{
		area += (points[j].X + points[i].X) * (points[j].Y - points[i].Y);
		j = i;

		int k = (i + 1) % n;
		double dx = points[k].X - points[i].X;
		double dy = points[k].Y - points[i].Y;
		length += sqrt(dx * dx + dy * dy);
		if (stringRightPoint.X < points[i].X)
		{
			stringRightPoint.X = points[i].X;
			stringRightPoint.Y = points[i].Y;
		}
		if(stringLeftPoint.X > points[i].X)
		{
			stringLeftPoint.X = points[i].X;
			stringLeftPoint.Y = points[i].Y;
		}
	}

	area = (abs(area) / 2.0);
	HDC hdc = ::GetDC(NULL);
	int horzRes  = ::GetDeviceCaps(hdc, HORZRES);
	int vertRes  = ::GetDeviceCaps(hdc, VERTRES);
	int horzSize = ::GetDeviceCaps(hdc, HORZSIZE);
	int vertSize = ::GetDeviceCaps(hdc, VERTSIZE);
	::ReleaseDC(NULL, hdc);
	double dotsPerInch = (sqrt(pow(horzRes, 2) + pow(vertRes, 2)) / sqrt(pow(horzSize, 2) + pow(vertSize, 2)) * 25.4);

	m_area   = (area * (25.4f / dotsPerInch) * (25.4f / dotsPerInch));
	m_length = (length * (25.4f / dotsPerInch));

	std::stringstream areaStream;
	areaStream << std::fixed << std::setprecision(2) << m_area;
	std::string areaString = areaStream.str();
	std::wstring areaWString (areaString.begin(), areaString.end());
	areaWString += L" mm²";
	areaWString.insert(0,L"Area = ");
	Gdiplus::Font stringMMFont(&Gdiplus::FontFamily(MM_STRING_FONT_FACE), static_cast<Gdiplus::REAL>(MM_STRING_FONT_SIZE), Gdiplus::FontStyleBold, Gdiplus::UnitPixel);
	Gdiplus::SolidBrush stringMMBrush(MM_STRING_COLOR);
	Gdiplus::RectF stringAreaBounds;
	drawerGraphics.MeasureString(areaWString.c_str(), static_cast<INT>(areaWString.length()), &stringMMFont, Gdiplus::PointF(0, 0), &stringAreaBounds);

	std::stringstream lengthStream;
	lengthStream << std::fixed << std::setprecision(2) << m_length;
	std::string lengthString = lengthStream.str();
	std::wstring lengthWString(lengthString.begin(), lengthString.end());
	lengthWString += L" mm";
	lengthWString.insert(0, L"length = ");
	Gdiplus::RectF stringLengthBounds;
	drawerGraphics.MeasureString(lengthWString.c_str(), static_cast<INT>(lengthWString.length()), &stringMMFont, Gdiplus::PointF(0, 0), &stringLengthBounds);

	Gdiplus::REAL width = 0;
	if (stringAreaBounds.Width > stringLengthBounds.Width)
		width = stringAreaBounds.Width;
	else
		width = stringLengthBounds.Width;

	if (stringRightPoint.X + width >= canvasRect.right)
	{
		if (stringLeftPoint.Y + stringAreaBounds.Height + stringLengthBounds.Height > canvasRect.bottom)
			stringLeftPoint.Y = static_cast<INT>(stringLeftPoint.Y-(stringAreaBounds.Height + stringLengthBounds.Height));
		if (stringLeftPoint.X - width < canvasRect.left)
			width = 0;
		drawerGraphics.DrawString(areaWString.c_str(), static_cast<INT>(areaWString.length()), &stringMMFont,
								  Gdiplus::PointF(static_cast<Gdiplus::REAL>(stringLeftPoint.X - width), static_cast<Gdiplus::REAL>(stringLeftPoint.Y)), &stringMMBrush);
		drawerGraphics.DrawString(lengthWString.c_str(), static_cast<INT>(lengthWString.length()), &stringMMFont,
								  Gdiplus::PointF(stringLeftPoint.X - width, stringLeftPoint.Y+ stringAreaBounds.Height), &stringMMBrush);
	}
	else
	{
		if (stringRightPoint.Y + stringAreaBounds.Height + stringLengthBounds.Height > canvasRect.bottom)
			stringRightPoint.Y = static_cast<INT>(stringRightPoint.Y - (stringAreaBounds.Height + stringLengthBounds.Height));
		drawerGraphics.DrawString(areaWString.c_str(), static_cast<INT>(areaWString.length()), &stringMMFont,
								  Gdiplus::PointF(static_cast<Gdiplus::REAL>(stringRightPoint.X), static_cast<Gdiplus::REAL>(stringRightPoint.Y)), &stringMMBrush);
		drawerGraphics.DrawString(lengthWString.c_str(), static_cast<INT>(lengthWString.length()), &stringMMFont,
								  Gdiplus::PointF(static_cast<Gdiplus::REAL>(stringRightPoint.X), stringRightPoint.Y+stringAreaBounds.Height), &stringMMBrush);
	}
}

void PolygonTool::setCrossLinePoints(const bool isStartPointCross, const CPoint& point, int frameNumber)
{
	if (isStartPointCross)
	{
		m_polygonParams_map[frameNumber].lineToolStartCross.first.first = Gdiplus::Point(point.x - POLYGON_TOOL_CROSS_LINE_LENGTH, point.y - POLYGON_TOOL_CROSS_LINE_LENGTH);
		m_polygonParams_map[frameNumber].lineToolStartCross.first.second = Gdiplus::Point(point.x + POLYGON_TOOL_CROSS_LINE_LENGTH, point.y + POLYGON_TOOL_CROSS_LINE_LENGTH);
		m_polygonParams_map[frameNumber].lineToolStartCross.second.first = Gdiplus::Point(point.x - POLYGON_TOOL_CROSS_LINE_LENGTH, point.y + POLYGON_TOOL_CROSS_LINE_LENGTH);
		m_polygonParams_map[frameNumber].lineToolStartCross.second.second = Gdiplus::Point(point.x + POLYGON_TOOL_CROSS_LINE_LENGTH, point.y - POLYGON_TOOL_CROSS_LINE_LENGTH);
	}
	else
	{
		m_polygonParams_map[frameNumber].lineToolEndCross.first.first = Gdiplus::Point(point.x - POLYGON_TOOL_CROSS_LINE_LENGTH, point.y - POLYGON_TOOL_CROSS_LINE_LENGTH);
		m_polygonParams_map[frameNumber].lineToolEndCross.first.second = Gdiplus::Point(point.x + POLYGON_TOOL_CROSS_LINE_LENGTH, point.y + POLYGON_TOOL_CROSS_LINE_LENGTH);
		m_polygonParams_map[frameNumber].lineToolEndCross.second.first = Gdiplus::Point(point.x - POLYGON_TOOL_CROSS_LINE_LENGTH, point.y + POLYGON_TOOL_CROSS_LINE_LENGTH);
		m_polygonParams_map[frameNumber].lineToolEndCross.second.second = Gdiplus::Point(point.x + POLYGON_TOOL_CROSS_LINE_LENGTH, point.y - POLYGON_TOOL_CROSS_LINE_LENGTH);
	}
}

void PolygonTool::drawPolygonEndPointCross(Gdiplus::Graphics& lineToolDrawerGraphics, int frameNumber)
{
	Gdiplus::Pen crossDrawingPen(CROSS_DRAWING_PEN_COLOR, CROSS_DRAWING_PEN_WIDTH_IN_PIXEL);
	crossDrawingPen.SetDashStyle(Gdiplus::DashStyle::DashStyleSolid);
	lineToolDrawerGraphics.DrawLine(&crossDrawingPen, m_polygonParams_map[frameNumber].lineToolStartCross.first.first, m_polygonParams_map[frameNumber].lineToolStartCross.first.second);
	lineToolDrawerGraphics.DrawLine(&crossDrawingPen, m_polygonParams_map[frameNumber].lineToolStartCross.second.first, m_polygonParams_map[frameNumber].lineToolStartCross.second.second);
	lineToolDrawerGraphics.DrawLine(&crossDrawingPen, m_polygonParams_map[frameNumber].lineToolEndCross.first.first, m_polygonParams_map[frameNumber].lineToolEndCross.first.second);
	lineToolDrawerGraphics.DrawLine(&crossDrawingPen, m_polygonParams_map[frameNumber].lineToolEndCross.second.first, m_polygonParams_map[frameNumber].lineToolEndCross.second.second);
}


here polygon tool is the child class of BaseTool and how will we implement Finite State Machine concept using state design pattern such that control of the states or bool variables can be controlled by FSM