Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

My Photo Editor

0.00/5 (No votes)
25 Jul 2012 6  
This is about how to make a simple sofware like photoshop using Visual Studio C++ and OpenCV

Introduction

In this article I will show you how to make a software like Photoshop except simpler, smaller and easier to use. You can load an image, change it into many color spaces (RGB, HSV, YCrCb), adjust brightness/contrast, do some filter (Noise, Blur ...), equalize histogram, view image information and so on. Image after processing can be saved with an optional quality.

This small software was written using Microsoft Visual Studio 2010 and OpenCV library (version 2.4.1). To use the code, you must have some basic knowledge in image processing, know how to use MFC in Visual Studio as well as OpenCV.

Using the Code 

I divide this program into 3 parts: image processing core class, image preview class and main processing.  

The Image processing core class contains many processing functions like adjust brightness, add noise, detect edge, rotate image and so on.  All these functions have a form like this:    

// Image processing function in header file
void Brightness(cv::Mat &src, cv::Mat &dst, int val)

Where src is source image, dst is image after processing and val is value for a specific function. If We name the header file the ImgProcessing, then in the implementation file, the function above looks like this:  

// ImgProcessing.cpp  file
void ImgProcessing::Brightness(Mat &src, Mat &dst, int val)
{
	if(src.channels() == 1)
	{
		for(int i = 0; i < src.rows; i++)
			for(int j = 0; j < src.cols; j++)
				dst.at<uchar>(i,j) = saturate_cast<uchar>(src.at<uchar>(i,j) + val);  
	}
 
	if(src.channels() == 3)
	{
		for(int i = 0; i < src.rows; i++)
			for(int j = 0; j < src.cols; j++)
				for(int k = 0; k < 3; k++)
					dst.at<Vec3b>(i,j)[k] = saturate_cast<uchar>(src.at<Vec3b>(i,j)[k] + val);
	}
}

The image preview class is inherited from CDialog class. This class has unity functions that allow us to change parameters and preview what image should be like after processing.

     

Two images above show cases of previewing (noise and distort image). In fact, there are many cases need to be previewed before any decision was made. Because each function has its own type and its own parameters, so the number of dialog preview will be nearly equal to the number of processing function! That means we must create a lot of dialog and spend more time to write the code. It is not a good choice. To avoid creating many preview dialogs and writing more code, we create just only one and give it some options. The appearance and the returned value of preview dialog will depends on what the main process told it to do.

For example, suppose we are processing the brightness of an image, when the brightness adjustment menu is clicked, the image preview dialog will appear. And the OnInitDialog function will be defined as the following:

BOOL ImgPreview::OnInitDialog()
{
	
	// Init components
	this->slider1.SetRange(0, 450);
	this->slider1.SetPos(225);
	this->slider2.SetRange(0, 450);
	this->slider2.SetPos(225);
	this->slider3.SetRange(0, 450);
	this->slider3.SetPos(225);
 
	this->slider2.EnableWindow(false);
	this->slider3.EnableWindow(false);
	
	
	switch(type)
	{
		case m_brightness:
		{
			this->param1.SetWindowTextA("Brightness");
		}
		break;
 
		.....
        }
	return true;
}

When we change the position of slider on image preview dialog, the brightness of the image must be changed and displayed on the picture box. There are ways to display an image on controls in MFC (eg. picture control). In this article, I use a special unity function of MFC to force image to display.  So let's create an Picture Control or arbitrary control you like. Change the ID of control to IDC_STATIC_IMG_PREVIEW and when the dialog is initialized, put the following code to OnInitDialog function:

BOOL ImgPreview::OnInitDialog()
{
	
	// Init components
	//....
	cv::namedWindow("Image Preview", 1);
	HWND hWnd = (HWND) cvGetWindowHandle("Image Preview");
	HWND hParent = ::GetParent(hWnd);
	::SetParent(hWnd, GetDlgItem(IDC_STATIC_IMG_PREVIEW)->m_hWnd);
	::ShowWindow(hParent, SW_HIDE);
	//....
	return true;
}

Until now, each time we call the cv::imshow("Image Preview", image)function, It will force the picture control to show the image. Now, the code when changing slider position will be as following:  

void ImgPreview::OnNMCustomdrawSlider1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
	// TODO: Add your control notification handler code here
	cv::Mat img_dst = img_display.clone();
	switch(type)
	{
	case m_brightness:
	{
		i_param1 = slider1.GetPos() - (int)slider1.GetRangeMax()/2;
		l_disp1.SetWindowTextA(ToString(i_param1));
		process.Brightness(img_display, img_dst, i_param1);
		cv::imshow("Image Preview", img_dst);
		
	}
        // Other cases	
	//...
}}

img_display is an image which resized from source image to be smaller to display on preview dialog.

If the OK button is pressed, the is_ok member of ImgPreview class will be set to true to let the main process know that everything seems alright. Otherwise, it will be set to false (This is one way two dialogs communicate).

The main process is based on MFC dialog with a menu system, buttons and other controls. Let's create a menu system like this: 

 

And then add event handler to each menu. The invert event handler looks like this:

void CMyPhotoEditorDlg::OnUpdateAdjustmentsInvert(CCmdUI *pCmdUI)
{
	// Invert Image
	process.Invert(src, src);
	ImageDisplay(src);
	.....
}  

Where process is an instance of ImgProcessing class, and ImageDisplay() is a function forced image to display in MFC control.

If menu that process an image need to be previewed, menu event handlers will look this:

void CMyPhotoEditorDlg::OnUpdateAdjustmentsBrightness(CCmdUI *pCmdUI)
{
	// Adjust brightness
	ImgPreview dlg;
	dlg.SetType(m_brightness);
	dlg.src = src;
	dlg.DoModal();
	if(dlg.is_ok)
	{
		process.Brightness(src, src, dlg.i_param1);
		ImageDisplay(src);
		
		.....
	}
}

Another important task is to validate menu system. If not, some errors may occur. For example, if we try to convert image that are in grayscale mode to hsv mode or if we try to modify image that has not been loaded (ie empty image), program may crash. Validate menu function is as below:

void CMyPhotoEditorDlg::ValidateMenu()
{
	// Validate menu
	CMenu *m_file = GetMenu();
	m_file->GetSubMenu(0);
 
	CMenu *m_image = GetMenu();
	m_image->GetSubMenu(1);
 
	....
 
	if(!is_load)
	{
		m_file->EnableMenuItem(ID_FILE_SAVE1, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_GRAYSCALE, MF_DISABLED|MF_GRAYED);
		....
	}
	
	else
	{
		m_file->EnableMenuItem(ID_FILE_SAVE1, MF_ENABLED);
		m_image->EnableMenuItem(ID_MODE_GRAYSCALE, MF_ENABLED);
		....
	}
	if(is_gray)
	{
		m_image->EnableMenuItem(ID_MODE_HSV, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_YCrCb, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_RGBCOLOR, MF_DISABLED|MF_GRAYED);
	}
	if(is_hsv)
	{
		m_image->EnableMenuItem(ID_MODE_HSV, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_GRAYSCALE, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_YCrCb, MF_DISABLED|MF_GRAYED);
	}
	
        ....
}

The view menu contains image information, histogram and history. If these menus are selected, they are then set to checked and updated after each step.

void CMyPhotoEditorDlg::OnUpdateAdjustmentsBrightness(CCmdUI *pCmdUI)
{
	// Brightness
	ImgPreview dlg;
	...
	dlg.DoModal();
	if(dlg.is_ok)
	{
		....
		if(is_histogram) UpdateHistogram(0);
		if(is_history) history_list.AddString("Adjust Image Brightness");
	}
}

Note that the explanation above is simple and just give some main ideas, many codes accompanied with comments are easy to understand and use.

History   

Version 1.0.   

This is the simplest program. I will comeback with an updated version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here