Бесплатная реклама

Автор: Евгений Бронников

Опубликовано: 06.11.2007

Изменено: 06.11.2007

Постоянная ссылка

Комментарии [0]

Графические спецэффекты в SDL





Страницы: 1 2

3. Осветление и затемнение

Данные эффекты позволяют изменять яркость заданного изображения: делать его темнее или светлее. Как вы уже знаете, каждый пиксель RGB-изображения содержит значения цветовых компонентов: красного, зеленого и синего. Чем меньше это значение, тем темнее отображаемый цвет и наоборот, чем больше значение, тем цвет светлее. Воспользовавшись этим знанием, мы можем изменять значение каждого компонента на равную величину, чтобы сделать весь пиксель светлее или темнее. Важно использовать одинаковую величину для изменения значения каждого компонента, поскольку цвет пикселя формируется всеми тремя компонентами и изменение любого из них приведет к потере первоначального цвета.

И так, чтобы реализовать эти эффекты нам нужно увеличить или уменьшить значение каждого цветового компонента пикселя на одинаковую величину. Опытным путем были выбраны способы трансформации для осветления и затемнения изображения, о которых я собираюсь вам рассказать.

Для затемнения пикселя отсечем два старших бита в значении цветового компонента. К примеру, если компонент имеет значение 0xFF (или 11111111 в двоичной системе счисления), то отсечение двух старших битов однобайтной величины выглядит так:

(0xFF & 0xFC) >> 2

Чтобы точнее понять это действие, распишем его в двоичном виде по действиям:

1)  11111111 & 11111100 = 11111100 (отсекли два младших бита)

2)  11111100 >> 10 = 00111111 (отсекли два старших бита)

На практике оказалось, что отсечение двух старших битов значения каждого цветового компонента окажет существенное влияние на исходный цвет пикселя – затенение будет достаточно сильным. Поэтому добавим третье действие – просто умножим полученное значение на 3. Итоговая формула для преобразования каждого компонента такова:

((Ch & 0xFC) >> 2) * 3

Где переменная Ch – каждый цветовой компонент пикселя (R, G, B).

При осветлении пикселя мы должны увеличить значения его цветовых компонентов, по следующей формуле, также подобранной опытным путем:

((Ch & 0xFE) >> 1) + 0xFE / 2

Для значения размером в один байт все достаточно просто. Сложность возникает для пикселей с глубиной цвета 15 и 16 бит (555 и 565). Чтобы наш эффект выглядел одинаково для всех режимов, был подобран коэффициент для 15 и 16 битных режимов, с учетом того, что на каждый компонент приходится по5 или 6 бит. Можно воспользоваться полями [RGB]loss, описанными раннее, но при этом будет тратиться какое-то количество ресурсов на восстановление и упаковку цветовых компонент. Реализацию эффектов для 15 и 16-битных изображений вы можете посмотреть в прилагаемом к статье коде.

Осталось рассмотреть пример реализации:

void dark_32bpp(void* pixels, int len, int w, int h)
{
	Uint32* src;
	int row = 0, col = 0;

	/* Для каждой строки изображения: */

	for(row = 0; row < h; row ++)
	{
		/* получить указатель на начало текущей строки */
		src = (Uint32 *)((int)pixels + row * len);

		/* Применить преобразование к каждому пикселю: */
		for(col = 0; col < w; col ++, src ++)
		{
			src[0] = 3*( (src[0] & 0xfcfcfcfc) >> 2 );
		}
	}
}
Листинг 3.1. Пример реализации затемнения для 32-битного изображения.
void light_32bpp(void* pixels, int len, int w, int h)
{
	Uint32* src;
	int row = 0, col = 0;

	/* Для каждой строки изображения: */
	for(row = 0; row < h; row ++)
	{
		/* получить указатель на начало текущей строки */

		src = (Uint32 *)((int)pixels + row * len);

		/* Применить преобразование к каждому пикселю: */
		for(col = 0; col < w; col ++, src ++)
		{
			src[0] = ((src[0] & 0x00fefefe) >> 1 ) + 0x00fefefe / 2;
		}
	}
}
Листинг 3.2. Пример реализации осветления для 32-битного изображения.

Вы можете самостоятельно изменить код, чтобы иметь возможность регулировать степень осветления и затемнения. Реализацию эффектов для 15, 16, 24 и 32-битных изображений вы можете найти в прилагаемых к статье исходниках.

4. Насыщенность цвета

Изменение насыщенности цвета является одним из любопытных эффектов. Прежде чем рассмотреть его практическую реализацию, сформулируем задачу более точно. Результатом этого эффекта должна стать некая функция, которая изменяет насыщенность цвета исходного изображения от оригинального до оттенков серого, по аналогии с телевизорами: черно-белый телевизор - старый цветной телевизор с более тусклыми цветами - современный телевизор с яркими насыщенными цветами. Для управления процентом насыщенности цвета будет использоваться некий параметр – регулятор. Из этой задачи вытекают несколько вопросов. Как получить тот самый оттенок серого из цветного пикселя? И как, наконец, получить среднее значение между оттенком серого и оригинальным цветом? Не секрет, что три одинаковых источника – красный, зеленый и синий – излучающие с одинаковой силой, в сумме дают белый цвет. В этом случае, яркость зеленого компонента будет максимальной, яркость красного компонента поменьше, а синего – минимальная. Подробнее о теории цвета вы можете узнать из Colorspace-FAQ, доступного по адресу http://www.inforamp.net/~poynton/Poynton-color.html. Таким образом, для любого RGB цвета можно вычислить значение яркости, используемое для конвертирования в оттенок серого (изображение в оттенках серого вместо значения цвета в каждом пикселе содержит значение его яркости). Для вычислений яркости можно воспользоваться коэффициентами веса для каждого компонента:

Brightness = 0.212671 * R + 0.715160 * G + 0.072169 * B;

В некоторых случаях для вычисления значения яркости цвета используют среднее арифметическое значений компонентов:

Brightness = (R + G + B) / 3;

Полученное значение является приближенным к реальному, но, говорят, что человеческий глаз практически не улавливает эту погрешность.

Теперь рассмотрим второй вопрос – как правильно вычислить значение цвета (вернее, его составляющих – RGB), в зависимости от коэффициента-регулятора насыщенности. Можно воспользоваться специальной функцией mix, которая имеет следующий алгоритм:

mix = X * (1 – A) + Y * A;

Эта функция возвращает значение, лежащее между X и Y, в зависимости от параметра A, который в нашем случае является регулятором насыщенности. Аргумент X в нашем случае будет значением яркости (оттенок серого), а аргумент Y – цвет пикселя оригинального цветного изображения. Таким образом, регулятор A задает долю составляющей цвета : единичка – цвет, ноль – оттенок серого.

Теперь мы можем перейти к рассмотрению алгоритма нашей функции, которая возвращает новую поверхность SDL с модифицированной насыщенностью цвета. Для каждого пикселя изображения вычисляется значение яркости одним из вышеперечисленных способов. Затем, каждому компоненту пикселя присваивается значение, возвращаемое функцией mix в зависимости от коэффициента насыщенности. Это, собственно, и все, что требуется выполнить. Достаточно просто, не правда ли? Рассмотрим реализацию этого эффекта для 16-битного режима.

/* Инициализируем нужные переменные */
void* pixels = surface->pixels;
int len = surface->pitch;

SDL_PixelFormat *fmt = surface->format;
Uint16* src;
int row = 0, col = 0;
int w = surface->w;
int h = surface->h;

/* Устанавливаем пороговое значение яркости */
if(brightness > 1.0) brightness = 1.0;


/* нормируем значение яркости к 255 для удобства */
int a = (int)(255 * brightness);

/* Проходим по каждой строке изображения */
for(row = 0; row < h; row ++)
{
	/* значение src – указатель на первый пиксель строки */
	src = (Uint16 *)((int)pixels + row * len);

	/* Проходим по каждому пикселю текущей строки */

	for(col = 0; col < w; col ++, src ++)
	{
		/* Раскодируем значения цветовых компонент пикселя */
		unsigned char src_red;
		unsigned char src_green;
		unsigned char src_blue;
		SDL_GetRGB(src[0], fmt, &src_red, &src_green, &src_blue);

		/* Вычисляем яркость пикселя по формуле, */
		/* но используем нормированные к 255 значения */

		int B = (src_red * 54 + src_green * 182 + src_blue * 18) / 255;

		/* Смешиваем значения оттенка серого и цвета */
		int b = (B * (255 - a) + src_blue * a) / 255;
		int g = (B * (255 - a) + src_green * a) / 255;
		int r = (B * (255 - a) + src_red * a) / 255;

		/* Записываем новое значение пикселя */
		/* используя вычисленные значения цветовых компонент */

		src[0] = SDL_MapRGB(fmt, r, g, b);
	}
}
Листинг 4.1. Пример реализации эффекта.

Значения цветовых компонент пикселя удобно извлекать при помощи функции SDL_GetRGB, ровно как и вычислять значение пикселя по заданным значениям цветовых компонент при помощи функции SDL_MapRGB. Можно получать и записывать эти значения вручную, как сделано в реализации данного эффекта для режимов 24 и 32 бита на пиксель (см. исходный код приложения).

5. Растворение

Следующий эффект, предлагаемый вашему вниманию, позволяет растворять (blending) одно изображение в другом. Выглядит это так, как будто поверх основного изображения накладывают полупрозрачное, но выполняется все программно. Алгоритм этого эффекта очень похож на рассмотренный выше. В качестве параметров функции, реализующий данный алгоритм, мы будем передавать два изображения – основное и дополнительное, расположенное поверх основного. Для управления глубиной смешивания будем использовать некий параметр factor, который обратно пропорционален значению альфа для всей плоскости дополнительного изображения – чем меньше значение, тем больше «непрозрачность».

И так, наша функция создает и возвращает новое изображение, которое является «смесью» из двух исходный изображений, смешанных в заданной пропорции. Для нового изображения необходимо вычислить каждый пиксель по формуле, упоминавшийся выше:

mix = X * (1 – A) + Y * A;

В качестве переменной X мы будем использовать пиксель изображения номер 2 (верхний слой), а в качестве переменной Y – нижний слой. Очевидно одно из ограничений данной реализации – исходные изображения должны иметь идентичные размеры и формат. Рассмотрим пример для RGB поверхностей с глубиной цвета 16 бит на пиксель. Данный код записывает изменения в переданную в параметрах поверхность surface1:

/* Инициализируем нужные переменные */
int len = surface1->pitch;
SDL_PixelFormat *fmt = surface1->format;
unsigned char *src1, *src2;

int row = 0, col = 0;
int w = surface1->w;
int h = surface1->h;

/* Устанавливаем пороговое значение параметра смешивания */
if(factor > 1.0) factor = 1.0;

/* нормируем значение параметра смешивания к 255 для удобства */
int a = (int)(255 * factor);


/* Проходим по каждой строке изображения */
for(row = 0; row < h; row ++)
{
	/* значение src1 – указатель на первый пиксель строки */
	/* нижнего слоя, а src2 - верхнего */
	src1 = (Uint16 *)((int)surface1->pixels + row * len);
	src2 = (Uint16 *)((int)surface2->pixels + row * len);

	/* Проходим по каждому пикселю текущей строки */
	/* каждого изображения */

	for(col = 0; col < w; col ++, src1 ++, src2 ++)
	{
		/* Раскодируем значения цветовых компонент пикселя */
		/* каждой поверхности: */
		unsigned char src1_red;
		unsigned char src1_green;
		unsigned char src1_blue;
		SDL_GetRGB(src1[0], fmt, &src1_red, &src1_green, &src1_blue);

		unsigned char src2_red;
		unsigned char src2_green;
		unsigned char src2_blue;
		SDL_GetRGB(src2[0], fmt, &src2_red, &src2_green, &src2_blue);

		/* Вычисляем яркость пикселя по формуле, */
		/* но используем нормированные к 255 значения */

		int B = (src_red * 54 + src_green * 182 + src_blue * 18) / 255;

		/* Смешиваем значения каждого цветового компонента */
		/* исходных изображений */
		int b = ((int)src2_blue  * (255 - a) + (int)src1_blue  * a) / 255;
		int g = ((int)src2_green * (255 - a) + (int)src1_green * a) / 255;
		int r = ((int)src2_red   * (255 - a) + (int)src1_red   * a) / 255;

		/* Записываем новое значение пикселя */
		/* используя вычисленные значения цветовых компонент */

		src1[0] = SDL_MapRGB(fmt, r, g, b);
	}
}
Листинг 5.1. Пример реализации эффекта.

Реализация эффекта довольно сильно напоминает предыдущий (раздел 4), за исключением того, что вместо вычисленного значения оттенка серого используется пиксель из второго изображения. Код эффекта для изображений глубиной 24 и 32 бита на пиксель вы найдете в приложении.


Примечания

Исходные коды (97кб)

Все исходные коды перечисленных спецэффектов вы можете найти в приложении к этому документу. Все функции вынесены в отдельный модуль (gfx.c и gfx.h) для удобства использования в программах на языке Си и С++. Описание каждой функции дано в комментариях к коду. Последние версии этого и других документов, а также архивов с исходными кодами к статье вы можете найти на сайте сообщества программистов игр для Linux: http://plg.lrn.ru. Условия использования и распространения исходного кода вы можете узнать из файла README в архиве. Все упомянутые торговые марки, товарные знаки и прочее являются собственностью их владельцев.

Для написания документа были использованы следующие источники:
1. Colorspace FAQ
2. Документация библиотеки SDL. http://libsdl.org

Страницы: 1 2