Предисловие
Сидел я и программировал разнообразную ерунду, а именно графические фильтры и все что с ними связано, на 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;
}
Вот собственно и все!
Комментарии (24)
rss свернуть / развернутьтопик на всю страницу, УЖОС ПРОСТО!!!
а топик супер)
свернуть ветку
свернуть ветку
топик на всю страницу, УЖОС ПРОСТО!!!
а топик супер)
свернуть ветку
Я как то подумывал, хм..), хотя честно говоря хочу, написать кросс платформенную прогу которая будет использовать OGL и DevIL для просмотра изображении и некоторых преобразовании(с возможностью сохранения), но все никак руки не дотянуться.
Да еще жесткий посыпался, и вся литература, либы, проекты, заготовки тож «посыпались»((( Ща сижу на Ubuntu с флехи(4 гига) памяти в обрез, работает с подвисонами.(( эх мне б жесткий)))
А ща тут классный код. Буду теперь брать за основу, если руки все же дотянуться писать. Если ты не против.
свернуть ветку
свернуть ветку
Просто у меня была проблема, я линь ставил, потом удалил, поставил Виндовс. И один раздел не видело(в Ntfs).Я его удалил, потом форматнул(быстрое форматирование). А потом через какую-то прогу(вроде Partion manager) восстановил все файлы на другой раздел.
Она вроде и раздел с после Linux(ext4) видела и могла восстанавливать.
Может типо такого есть на Linux?.. хотя может уже поздно((
свернуть ветку
свернуть ветку
Сочувствую.
Мне хотя и очень обидно за свой жесткий, но он мне верно прослужил около 5-6 лет. Вот — вот собирался новый брать и тут бац, и ппц.
свернуть ветку
свернуть ветку
свернуть ветку
Кстати вроде тут C, а на C++, так не одна из возможностей с++ не использована. А если переделать твою основу в класс, это могло увеличить производительность, так как у тебя инициализация DevIL при каждом вызове функции, а вызовов может быть много.
Это можно сделать один раз в конструкторе класса, и еще не плохо было бы сделать, чтоб класс использовался как контейнер для данных изображения.
свернуть ветку
new, delete — выделение\удаление памяти в стиле С++.
malloc(), free() — выделение\удаление памяти в стиле C.
а на счёт классов ты предлагаешь сделать как в шарпе Bitmap?.. хотя реализация классов в с++ мне не очень нравится.
свернуть ветку
свернуть ветку
Извините.
И, блин, меня учили, что new delete это C, меня ввели в заблуждение(.
Все буду помалкивать.
свернуть ветку
свернуть ветку
свернуть ветку
свернуть ветку
свернуть ветку
свернуть ветку
свернуть ветку
свернуть ветку
Я просто высказал, мнение, хоть и неверное.
Просто не прислушивайтесь, скажите че я не прав.
Че так сразу нападать?!
свернуть ветку
свернуть ветку
свернуть ветку