Click here to Skip to main content
Click here to Skip to main content

My Photo Editor

, 25 Jul 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Long Nguyen (xyz)
Software Developer (Senior) TOSY ROBOTICS JSC
Vietnam Vietnam
I have Bachelor of Industrial Information from HaNoi University of Science and Technology.
 
I am currently working in the field of image processing and computer vision with 3 years of experience.

Comments and Discussions

 
QuestionSuperb Pinprofessionalzlogdan10-Jun-13 6:31 
AnswerRe: Superb PinmemberLong Nguyen (xyz)13-Nov-13 16:33 
QuestionHow can I add a scroll bar to big images PinmemberShibu Krishnan2-Mar-13 11:33 
GeneralA big lol. No, really Pinmemberxawari18-Aug-12 2:27 
GeneralMy vote of 5 PinmemberUnque31-Jul-12 1:28 
GeneralRe: My vote of 5 PinmemberLong Nguyen (xyz)31-Jul-12 1:51 
QuestionNice job, even though the claim is too high Pinmembernv330-Jul-12 2:56 
GeneralMy vote of 5 Pinmemberkanalbrummer30-Jul-12 1:41 
GeneralRe: My vote of 5 PinmemberLong Nguyen (xyz)31-Jul-12 1:53 
GeneralMy vote of 5 PinmemberNoOne_28-Jul-12 16:03 
Questionrun errors Pinmemberalannx27-Jul-12 22:12 
AnswerRe: run errors PinmemberLong Nguyen (xyz)27-Jul-12 22:54 
GeneralRe: run errors Pinmemberalannx28-Jul-12 0:07 
GeneralRe: run errors PinmemberLong Nguyen (xyz)31-Jul-12 1:55 
QuestionIt's great! Pinmemberchungelect25-Jul-12 17:23 
AnswerRe: It's great! PinmemberLong Nguyen (xyz)25-Jul-12 17:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411023.1 | Last Updated 25 Jul 2012
Article Copyright 2012 by Long Nguyen (xyz)
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid