Простые алгоритмы работы с изображением
Цвета.
Для написания хорошего графического редактора, необходимо добавить такую простую функцию, как фильтр. Он позволяет изменить какие-то свойства изображения.
Давайте посмотрим, какие фильтры можно применить к цветам картинки. Первым делом нам необходимо получить массив пикселей, для дальнейшей работы с ними. Так как цветовые фильтры требуют работы с каждым отдельным пикселем, в нашем коде будет использоваться цикл, перебирающий пиксели и пременяющий фильтр:
Подготовочные действия
Цикл перебора по X, Y
Применение формулы
Конец цикла и заключительные действия
Итак: подготовительные действия
C++ |
1
2
3
| Byte* ptr; //Это объявление необходимо только для первого способа перебора
Image->PixelFormat = pf24bit; // Наиболее оптимальный формат пикселей для наших фильтров
StatusBar1->SimpleText = "Подождите пожалуйста"; |
|
В VCL существует два варианта циклов перебора:
C++ |
1
2
3
4
5
6
7
| for(int y = 0; y < Image->Height; y++) //Перебор строк
ptr = (Byte*)Image->ScanLine[y]; //Получение текущей строки пикселей
for (int x = 0; x < Image->Width * 3; x+=1) //Перебор каждого пикселя из строки
//RGB:
ptr[x] = ... //Красный
ptr[x+1] = ... //Зеленый
ptr[x+2] = ... //Синий |
|
Этот способ достаточно быстрый, однако, при его использовании могут возникать некоторые проблемы. Второй способ проще для понимания:
C++ |
1
2
3
| for(int y = 0; y < Image->Height; y++) //Перебор строк
for (int x = 0; x < Image->Width; x++) //Перебор каждого пикселя из строки
Image->Pixels[x][y] = ... //Красный + зеленый + синий |
|
Рассмотрим сначала затемнение о осветление пикселей. В системе цветов RGB черным является цвет 0,0,0, а белым 255,255,255. Таким образом, при затемнении, каждый канал пиксела стремиться к 0,0,0 способом декрементирования. Также, следует учитывать тот факт, что если канал имеет значение 0, Декрементировать его нет смысла. Вот итоговый алгоритм:
C++ |
1
2
3
4
5
| for(int y = 0; y < Image->Height; y++)
ptr = (Byte*)Image->ScanLine[y];
for (int x = 0; x < Image->Width * 3; x++)
//Особое внимание на 3 бита на одну точку- Image->Canvas->Width * 3
if(ptr[x] > 10) ptr[x] = (Byte)(ptr[x] - 10); |
|
Полная версия алгоритма обладает одной интересной особенностью: по причине того, что над каждым каналом каждого пикселя совершается одно и тоже действие, полный цикл затемнения отличается от приведенного выше, однако, более оптимизированным считается код, выполняемый за меньшее число итераций. Вот оптимизированная версия алгоритма:
C++ |
1
2
3
4
5
6
7
8
| for(int y = 0; y < Image->Height; y++)
ptr = (Byte*)Image->ScanLine[y];
for (int x = 0; x < Image->Width * 3; x+=3)
//Особое внимание на 3 бита на одну точку- Image->Canvas->Width * 3
if(ptr[x] > 10 && ptr[x+1] > 10 && ptr[x+2] > 10)
ptr[x] = (Byte)(ptr[x] - 10);
ptr[x+1] = (Byte)(ptr[x+1] - 10);
ptr[x+2] = (Byte)(ptr[x+2] - 10); |
|
Аналогичен код осветления:
C++ |
1
2
3
4
5
6
7
8
| for(int y = 0; y < Image->Height; y++)
ptr = (Byte*)Image->ScanLine[y];
for (int x = 0; x < Image->Width * 3; x+=3)
//Особое внимание на 3 бита на одну точку- Image->Canvas->Width * 3
if(ptr[x] < 245 && ptr[x+1] < 245 && ptr[x+2] < 245)
ptr[x] = (Byte)(ptr[x] + 10);
ptr[x+1] = (Byte)(ptr[x+1] + 10);
ptr[x+2] = (Byte)(ptr[x+2] + 10); |
|
Можно также привести изображение к определенному оттенку. Для этого необходимо изменить соотношение между каналами. Например, если вы хотите сделать так, чтобы каждый канал содержал только оттенки красного, надо рассчитать среднее значение между тремя каналами, а затем установить каналы G и B в 0, а R в полученное значение:
C++ |
1
2
3
4
5
6
7
8
9
10
11
| int r = 0, g = 0, b = 0, color = 0;
for(int y = 0; y < Image->Height; y++)
ptr = (Byte*)Image->ScanLine[y];
for (int x = 0; x < Image->Width * 3; x+=3)
r = ptr[x]; //Получаем
g = ptr[x+1]; //значения
b = ptr[x+2]; //каналов
color = (r + g + b) / 3; //Выссчитываем среднее значение
ptr[x] = 0;
ptr[x+1] = 0;
ptr[x+2] = color; |
|
Фильтра "Оттенки серого" установливает значения всех каналов пикселя в одинаковые значения. Таким образом, все каналы уравновешены, и не добавляют в цвет пикселя "примеси".
C++ |
1
2
3
4
5
6
7
8
9
10
11
| int r = 0, g = 0, b = 0, color = 0;
for(int y = 0; y < Image->Height; y++)
ptr = (Byte*)Image->ScanLine[y];
for (int x = 0; x < Image->Width * 3; x+=3)
r = ptr[x]; //Получаем
g = ptr[x+1]; //значения
b = ptr[x+2]; //каналов
color = (r + g + b) / 3; //Выссчитываем среднее значение
ptr[x] = color;
ptr[x+1] = color;
ptr[x+2] = color; |
|
И, наконец, последний фильтр: "Инвертация цветов".
C++ |
1
2
3
4
5
6
| for(int y = 0; y < Image->Height; y++)
ptr = (Byte*)Image->ScanLine[y];
for (int x = 0; x < Image->Width * 3; x+=3)
ptr[x] = (Byte)(256 - ptr[x]);
ptr[x+1] = (Byte)(256 - ptr[x+1]);
ptr[x+2] = (Byte)(256 - ptr[x+2]); |
|
Примечание: в данной статье представлены неоптимизированные версии алгоритмов. Наиболее узкими местами являются: использование промежуточных переменных и постоянное использование свойств Image->Width и Image->Height в цикле, для них рекомендуется исользовать промежуточные переменные, желательно типа register.
Фильтра "Оттенки серого" установливает значения всех каналов пикселя в одинаковые значения.
Здесь советуют почитать про конвертацию RGB->YUV и забыть про среднее арифметическое.
Как Paint'е реализовано распыление и карандаш
В OnMouseMove.
Распыление
C++ |
1
2
3
4
5
6
7
8
9
10
| int x0, y0;
for(float i=1;i<BrushRad; i++) {
for(float f=1;f<360; f+=0.2) {
if(rand()>16384) {
x0 = X+cos(f)*i;
y0 = Y+sin(f)*i;
Image->Canvas->Pixels[x0][y0] = Image->Canvas->Brush->Color;
}
}
} |
|
КарандашC++ |
1
2
| Image->Canvas->Pen->Mode = pmCopy;
Image->Canvas->LineTo(X, Y); |
|