Обработка изображенийОбработка изображений в С++


Предисловие

Сидел я и программировал разнообразную ерунду, а именно графические фильтры и все что с ними связано, на C#. И потихоньку учил С++. Потом решил я сравнить производительность C# и C++.В тесте будут принимать участие самые распространённые графические фильтры: негатив, сепия, корректировка яркости и контрастности. Написал основу на С++ для загрузки/сохранения изображений с использованием DevIL, и перевел коды фильтров с шарпа на C++. Хотя эта основа наверное далека от идеала.
Вот тест производительности:
Тест производительности

Теперь можно посчитать во сколько раз увеличилась производительность:
Пусть
Формула

— функция подсчёта во сколько раз различается производительность C# и С++ кода, тогда
f(Negative) = 515 / 16 = 32,1875
f(Sepia) = 562 / 15 = 37,4(6)
f(Brightness) = 749 / 5 = 149,5
f(Contrast) = 780 / 63 = 12,38


Вот это прирост производительности, и даже без особой оптимизации, а вы представьте, если это все дело хорошенько оптимизировать? — получиться очень быстрый код.
Время выполнения кода измеряли функцией
Функция

C этого момента я буду писать уроки только на С++, но и поддержу пользователя с ником darkx, который предложил создавать кросс лэнг уроки в этом комменте. В каждом уроке будут исходники на языках VB.NET, C#.NET, C++, про Delphi ничего сказать не могу, так как очень давно на нём програмировал.

В VB.NET/C#.NET будет использоваться основа с названием Filter, об которой я расказывал в предыдущих уроках:
1) Графические фильтры на основе попиксельной обработки изображений (Часть 1)
2) Графические фильтры на основе попиксельной обработки изображений (Часть 2)
3) Графические фильтры на основе попиксельной обработки изображений (Часть 3)

А в С++ будет использоваться основа, о которой я счас расскажу.

Основа для программирования графических фильтров в С++


При программировании этой основы используется библиотека DevIL, о которой подробно рассказываетcя в этой статье.
Создайте у себя в проекте файлы:
1) Image.h — header file
2) Image.cpp — cpp code file

В файле Image.h будут храниться описания функций, а в файле Image.cpp — реализации функций. В названиях функций, констант вы встретите приставку «ag», вместо неё вы можете поставить любую другую.Это сделано для того чтобы не было дубликатов функций или констант в разных API.

Файл Image.h

#pragma once

#include <windows.h>
#include <il.h>                   //|
                                  // Подключаем DevIL
#pragma comment(lib, "DevIL.lib") //|


enum Pixelformat
{
	AG_BGR,  //Формат пикселей B|G|R
        AG_BGRA, //Формат пикселей B|G|R|A
        AG_RGB,  //Формат пикселей R|G|B
        AG_RGBA  //Формат пикселей R|G|B|A
};

enum ImageType
{
	AG_BMP = 0x0420,  //!< Microsoft Windows Bitmap - .bmp extension
	AG_JPG = 0x0425,  //!< JPEG - .jpg, .jpe and .jpeg extensions
	AG_PNG = 0x042A,  //!< Portable Network Graphics - .png extension
	AG_TGA = 0x042D,  //!< TrueVision Targa File - .tga, .vda, .icb and .vst extensions
	AG_TIF = 0x042E,  //!< Tagged Image File Format - .tif and .tiff extensions
	AG_JP2 = 0x0441   //!< Jpeg 2000 - .jp2 extension
};

typedef struct BaseIMGInfo
{
	int Depth;   //Глубина изобаржения(нужно только для 3D текстур), по умолчанию = 1
	int Format;  //Формат пикселей
	int Type;    //Тип данных в памяти, в основном = IL_UNSIGNED_BYTE
};

typedef struct ImageData
{
	unsigned char * Data; //Указатель на данные изобржения

	int Width;            //Ширина изображения
	int Height;           //Высота изображения
	int Bpp;              //Количество байт на пиксель
	int Stride;           //Количество байт в одной строке пикселей

	Pixelformat PixFormat;//Формат пикселей
	BaseIMGInfo BaseInfo; //Основные параметры изображения, нужные для сохранения изображений в DevIL

	int R_idx;            //Позиция красного в пикселе
	int G_idx;            //Позиция зелёного в пикселе
	int B_idx;            //Позиция синего в пикселе
	int A_idx;            //Позиция альфа-канала в пикселе, только для AG_RGBA или AG_BGRA
};

ImageData * agLoadImage(char * FileName); //Функция для загрузки изображения
bool agSaveImage(ImageData * IMG, char * FileName, ImageType IType); //Функция для сохранения изображения
ImageData * agNewImage(int Width, int Height,  Pixelformat PFormat); //Функция для создания нового,пустого, изображения
ImageData * agCloneImage(ImageData * Src); //Функция для создания копии изображения


Директива препроцесора #pragma once нужна, для контроля за тем, чтоб конкретный файл подключался при компиляции только один раз.Думаю больше никаких обьяснений к файлу Image.h не надо.Теперь перейдём к реализации.

Откройте файл Image.cpp, и включите в него Image.h:

#include "Image.h"

Теперь разберём каждую функцию; Объяснения к коду будут в комментариях.
1)Функция agLoadImage

ImageData * agLoadImage(char * FileName) //FileName - указатель на строку с именем файла
{
	ILuint * Texture = new ILuint; //Указатель на текстуру
	ImageData * Result = NULL; //Изображение

	ilInit();                //И и и л з ц я D v L
	ilEnable(IL_ORIGIN_SET); // н ц а и а и   е I
	ilGenImages(1, Texture); //Генерируем текстуру
	ilBindImage(*Texture);   //Делаем текстуру текущей

	if(ilLoadImage(FileName) == true) //Пытаемся загрузить изображение
	{ // если да, то
		Result = new ImageData(); //Создаём изображение
		
		Result -> Width = ilGetInteger(IL_IMAGE_WIDTH); //Получаем ширину изображения
		Result -> Height = ilGetInteger(IL_IMAGE_HEIGHT);//Получаем высоту изображения
		Result -> Bpp = ilGetInteger(IL_IMAGE_BYTES_PER_PIXEL);//Получаем количество байт на пиксель
		Result -> Stride = Result -> Width * Result -> Bpp;//Вычислем количество байт в одной строке пикселей

		Result -> Data = new unsigned char[Result -> Width * Result -> Height * Result -> Bpp]; //Создаём указатель на данные изображения

		

		memcpy(Result -> Data, ilGetData(), ilGetInteger(IL_IMAGE_SIZE_OF_DATA)); //Копируем данные из памяти в наше изображение

		Result -> BaseInfo.Type = ilGetInteger(IL_IMAGE_TYPE);//Получаем тип данных в памяти
		Result -> BaseInfo.Format = ilGetInteger(IL_IMAGE_FORMAT);//Получаем формат пикселей
		Result -> BaseInfo.Depth = ilGetInteger(IL_IMAGE_DEPTH);//Получаем глубину изображения

		if(Result -> BaseInfo.Format == IL_RGB) //Если формат пикселей BGR, то
		{                                       //данные располагаются в памяти
			Result -> R_idx = 0;            //так ->
			Result -> G_idx = 1;            //0|1|2|0|1|2|0|1|2|0|1
			Result -> B_idx = 2;            //R|G|B|R|G|B|R|G|B|R|G
		}
		else if(Result -> BaseInfo.Format == IL_RGBA) //Если формат пикселей RGBA,то
		{                                          //данные располагаются в памяти
			Result -> R_idx = 0;               //так ->
			Result -> G_idx = 1;               //0|1|2|3|0|1|2|3|0|1|2|3|0
			Result -> B_idx = 2;               //R|G|B|A|R|G|B|A|R|G|B|A|R
			Result -> A_idx = 3;
		}
		else if(Result -> BaseInfo.Format == IL_BGR) //Если формат пикселей BGR,то
		{                                            //данные располагаются в памяти
			Result -> R_idx = 2;                 //так ->
			Result -> G_idx = 1;                 //0|1|2|0|1|2|0|1|2|0|1|2
			Result -> B_idx = 0;                 //B|G|R|B|G|R|B|G|R|B|G|R
		}
		else if(Result -> BaseInfo.Format == IL_BGRA) //Если формат пикселей BGRA, то
		{                                          //данные располагаются в памяти
			Result -> R_idx = 2;               //так ->
			Result -> G_idx = 1;               //0|1|2|3|0|1|2|3|0|1|2|3|0
			Result -> B_idx = 0;               //B|G|R|A|B|G|R|A|B|G|R|A|B
			Result -> A_idx = 3;
		}
		else
		{
			MessageBoxA(0, "Unsuitable pixel format", "Error", MB_OK | MB_ICONERROR);
		}
	}
	else
	{ //Если нет, то выдаём сообщение об ошибке
		MessageBoxA(0, "Could not load image", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	ilDeleteImages(1, Texture); //Удаляем текстуру ->
	delete Texture;             //->|

	return Result;
}


2)Функция agSaveImage

bool agSaveImage(ImageData * IMG, char * FileName, ImageType IType) //IMG - изображение, FileName - имя сохраняемого файла, IType - расширение файла
{
	bool Res;//Результат функции
	if(IMG != NULL && strlen(FileName) > 0)//Если изображение не пустое и длинна имени файла больше нуля,то
	{
		ILuint * Texture = new ILuint;//Указатель на текстуру

		ilInit(); //Инициализация DevIL
		ilGenImages(1, Texture);//Генерируем текстуру
		ilBindImage(*Texture);  //Делаем её текущей

		ilTexImage(IMG -> Width,IMG -> Height,
				   IMG -> BaseInfo.Depth,IMG -> Bpp,IMG -> BaseInfo.Format,IMG -> BaseInfo.Type,
				   IMG -> Data);//Закидываем данные в память
		Res = ilSave(IType, FileName);//И сохраняем изображение

		ilDeleteImages(1, Texture); //Удаляем текстуру ->
		delete Texture;             //->|
	}
	else
	{ //Иначе,выдаем сообщене об ошибке
		MessageBoxA(0, "Pointer to the image is empty or the wrong name of the saved file", "Error", MB_OK | MB_ICONERROR);
	}
	return Res;
}


3)Функция agNewImage

ImageData * agNewImage(int Width, int Height,  Pixelformat PFormat)
{
	ImageData * Result = NULL;//Результат функции
	if(Width > 0 && Height > 0) //Если длинна и ширина - положительные величины, то
	{
		Result = new ImageData();//Создаем изображение

		Result -> Width = Width; // Присваиваем длинну
		Result -> Height = Height; // Присваиваем ширину
		Result -> PixFormat = PFormat; // Присваиваем формат пикселей

		Result -> BaseInfo.Depth = 1;
		Result -> BaseInfo.Type = IL_UNSIGNED_BYTE;

		if(Result -> PixFormat == AG_BGR)
		{
			Result -> R_idx = 2;
			Result -> G_idx = 1;
			Result -> B_idx = 0;
			Result -> BaseInfo.Format = IL_BGR; 
			Result -> Bpp = 3; //3 байта на пиксель, потомучто B(1 byte) + G(1 byte) + R(1 byte) = 3 bytes 
		}
		else if(Result -> PixFormat == AG_BGRA)
		{
			Result -> R_idx = 2;
			Result -> G_idx = 1;
			Result -> B_idx = 0;
			Result -> A_idx = 3;
			Result -> BaseInfo.Format = IL_BGRA;
			Result -> Bpp = 4; //4 байта на пиксель, потомучто B(1 byte) + G(1 byte) + R(1 byte) + A(1 byte) = 3 bytes
		}
		else if(Result -> PixFormat == AG_RGB)
		{
			Result -> R_idx = 0;
			Result -> G_idx = 1;
			Result -> B_idx = 2;
			Result -> BaseInfo.Format = IL_RGB;
			Result -> Bpp = 3;
		}
		else if(Result -> PixFormat == AG_RGBA)
		{
			Result -> R_idx = 0;
			Result -> G_idx = 1;
			Result -> B_idx = 2;
			Result -> A_idx = 3;
			Result -> BaseInfo.Format = IL_RGBA;
			Result -> Bpp = 4;
		}

		Result -> Data = new unsigned char[Result -> Width * Result -> Height * Result -> Bpp]; //Выделяем память под данные изображения
		Result -> Stride = Result -> Width * Result -> Bpp; //Вычисляем количество байт на строку пикселей
	}
	else
	{ //Иначе выдаем сообщение об ошибке
		MessageBoxA(0, "Invalid input parameters", "Error", MB_OK | MB_ICONERROR);
	}
	return Result;
}


4) Функция agCloneImage

ImageData * agCloneImage(ImageData * Src) //Src - исходное изображение
{
	ImageData * Dst = NULL; //Новое изображение
	if(Src != NULL) // Если исходное изображение не пустое,то
	{
		Dst = new ImageData();//Создаем новое изображение

		Dst -> B_idx = Src -> B_idx;// далее все просто копируем
		Dst -> G_idx = Src -> G_idx;
		Dst -> R_idx = Src -> R_idx;
		Dst -> A_idx = Src -> A_idx;

		Dst -> Width = Src -> Width;
		Dst -> Height = Src -> Height;
		Dst -> Stride = Src -> Stride;
		Dst -> Bpp = Src -> Bpp;
		Dst -> PixFormat = Src -> PixFormat;

		Dst -> BaseInfo.Depth = Src -> BaseInfo.Depth;
		Dst -> BaseInfo.Format = Src -> BaseInfo.Format;
		Dst -> BaseInfo.Type = Src -> BaseInfo.Type;
		try
		{
			Dst -> Data = new unsigned char[Dst -> Width * Dst -> Height * Dst -> Bpp];
			memcpy(Dst -> Data, Src -> Data, Dst -> Width * Dst -> Height * Dst -> Bpp);
		}
		catch(...)
		{
			MessageBoxA(0, "Can not allocate memory", "Error", MB_OK | MB_ICONERROR);
			delete Dst;
		}
	}
	else
	{ //иначе, выдаем сообщение об ошибке
		MessageBoxA(0, "Invalid input parameters", "Error", MB_OK | MB_ICONERROR);
	}
	return Dst;
}


Вот собственно и все!
  • +6
  • Alexei16
  • 01 августа 2011, 21:57

Вставка изображения

Комментарии (24)

rss свернуть / развернуть
  • avatar
  • isaer
  • 01 августа 2011, 22:02
  • #
  • 0
когда научитесь использовать

топик на всю страницу, УЖОС ПРОСТО!!!
а топик супер)

свернуть ветку
уже исправлено, вставлен

свернуть ветку
  • avatar
  • isaer
  • 01 августа 2011, 22:03
  • #
  • 0
когда научитесь использовать cut>

топик на всю страницу, УЖОС ПРОСТО!!!
а топик супер)

свернуть ветку
Супер!!!)
Я как то подумывал, хм..), хотя честно говоря хочу, написать кросс платформенную прогу которая будет использовать OGL и DevIL для просмотра изображении и некоторых преобразовании(с возможностью сохранения), но все никак руки не дотянуться.
Да еще жесткий посыпался, и вся литература, либы, проекты, заготовки тож «посыпались»((( Ща сижу на Ubuntu с флехи(4 гига) памяти в обрез, работает с подвисонами.(( эх мне б жесткий)))
А ща тут классный код. Буду теперь брать за основу, если руки все же дотянуться писать. Если ты не против.

свернуть ветку
как я тебя понимаю, я примерно часа 3 назад случано форматнул хард, представляешь какая херь была? это пиздец просто (сори за мат) у меня всего то это доки проекты серв и игра 50 метров, все удалилось меньше чем за 5 секунд, я даже ничего не успел сделать, это была порсто жопа какая то)) и не восстановилось обыдно что ничего)) придется все заново писать

свернуть ветку
Слушай на лине полное форматирование идет или быстрое(ну как на винде)?
Просто у меня была проблема, я линь ставил, потом удалил, поставил Виндовс. И один раздел не видело(в Ntfs).Я его удалил, потом форматнул(быстрое форматирование). А потом через какую-то прогу(вроде Partion manager) восстановил все файлы на другой раздел.
Она вроде и раздел с после Linux(ext4) видела и могла восстанавливать.
Может типо такого есть на Linux?.. хотя может уже поздно((

свернуть ветку
не знаю, я не совсем форматнул, удялял файл и случайно выделил все файлы, и по привычке shift del и все, ничего нету(

свернуть ветку
Нет способа восстановить?! ппц.
Сочувствую.
Мне хотя и очень обидно за свой жесткий, но он мне верно прослужил около 5-6 лет. Вот — вот собирался новый брать и тут бац, и ппц.

свернуть ветку
конечно я не против

свернуть ветку
Протести плиз шашки(

свернуть ветку
  • avatar
  • darkx
  • 02 августа 2011, 08:44
  • #
  • 0
Урок супер!
Кстати вроде тут C, а на C++, так не одна из возможностей с++ не использована. А если переделать твою основу в класс, это могло увеличить производительность, так как у тебя инициализация DevIL при каждом вызове функции, а вызовов может быть много.
Это можно сделать один раз в конструкторе класса, и еще не плохо было бы сделать, чтоб класс использовался как контейнер для данных изображения.

свернуть ветку
использована:
new, delete — выделение\удаление памяти в стиле С++.
malloc(), free() — выделение\удаление памяти в стиле C.
а на счёт классов ты предлагаешь сделать как в шарпе Bitmap?.. хотя реализация классов в с++ мне не очень нравится.

свернуть ветку
А насчет инициализации, это не плохая идея, но может просто функции init()… ИМх0)

свернуть ветку
Блин, что то я, переоценил свои знания, и включил поучилку.(
Извините.
И, блин, меня учили, что new delete это C, меня ввели в заблуждение(.
Все буду помалкивать.

свернуть ветку
помойму кому это надо будет то сделает, уроки должны учить а не делать за других!!!

свернуть ветку
полностью согласен.

свернуть ветку
ИМХО alt >(

свернуть ветку
сории за оч тупой вопрос, что такое ИМХО =)

свернуть ветку
И́МХО или IMHO (англ. IMHO), также имхо или imho (строчными буквами) — известное выражение, означающее «по моему́ скромному мнению» (англ. In My Humble Opinion или In My Honest Opinion).

свернуть ветку
ок спс, терь знаю что эт такое))

свернуть ветку
Протести плиз шашки

свернуть ветку
Но я ж не просил, сделать это!!! И тем более за меня!
Я просто высказал, мнение, хоть и неверное.
Просто не прислушивайтесь, скажите че я не прав.
Че так сразу нападать?!

свернуть ветку
никто ни на кого не наподает.

свернуть ветку
угу, просто вырозил мнение, и всо)

свернуть ветку
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.