Значение пиксела с таким цветом будет следующим (пробелы вставлены для удобочитаемости):
0001 1001 ОНО 0101
Все выглядит просто, имея значение трех составляющих, мы должны в пиксел заносить значение по следующей формуле:
blue + green * 2"5 + red * 2Л11 или blue + green * 64 + red * 4096
Операции умножения и деления с участием степени двойки лучше оптимизировать с помощью операции сдвига. Теперь окончательная формула выглядит так:
blue OR (green SHL 5) OR (red SHL 11)
Иллюстрация в виде примера последует позже, а сейчас задержимся на том, как вырезать из пиксела значения составляющих. Для этого применяются битовые маски. Так, для получения значения пяти битов красной составляющей надо использовать бинарное число
1111 1000 0000 0000
и логическую операцию AND для вырезания значения первых пяти битов. Вот так:
0001 1001 ОНО 0101 &
1111 1000 0000 0000
-------------------------------
0001 1000 0000 0000
Результат найден, как видим, верно, но ему предшествуют одиннадцать нулей. Чтобы получить значение составляющей, надо применить к этому выражению операцию битового сдвига вправо. Вот пример для красной составляющей:
Red : Byte;
Red := (pixel & $F800) SHR 11;
Или, если поменять порядок действий, вырезать ее можно так:
Red := (pixel SHR 11) AND $lf;
Маска в этом случае та же - пять единиц, но без завершающих одиннадцати нулей.
Перейдем к иллюстрации - проекту каталога Ех17. Работа его выглядит очень просто, на экране появляются вспышки синих и красных частиц. Работа с системой частиц во многом похожа на код предыдущего примера, но теперь воспользуемся концепцией ООП:
const
MAX ENERGY =60; // Максимальная энергия частицы
DEFAULT_SIZE =200; // Количество частиц во вспышке
DEFAULT_POWER =30; // Для зарядки энергии частицы
type
TParticle = record // Данные на отдельную частицу
X, Y : Single; // Позиция
SpeedX, SpeedY : Single; // Скорости по осям
Energy : Integer; // Энергия
Angle : Integer; // Направление движения
R, G, В : Byte; // Цвет
end;
TParticleSystem = class // Класс системы частиц
public
procedure Init (NewSize, Power : Integer); // Инициализация
procedure Calculate; // Пересчет положений частиц
function Render : HRESULT; // Отображение вспышки
private
Particle : Array [0..1000] of TParticle; // Массив частиц
Size : integer; // Размер
end;
Инициализация системы выглядит так:
procedure TParticleSystem.Init (NewSize, Power : Integer);
var
i : Integer;
X, Y : Integer; // Стартовая точка вспышки Speed : Single;
begin
Size := NewSize; // Устанавливаем размер системы
// Центр вспышки располагаем вдали от границ экрана
X := random (ScreenWidth - 80) + 40;
Y := random (ScreenHeight - 80) + 40;
for i := 0 to Size do begin // Частицы системы
Particle[i].X := X;
Particle[i].Y := Y;
Particle[i].Energy := random (MAX_ENERGY); // Энергия
Particle[i].Angle := random (360); // Угол движения
Speed := random (Power) - Power / 2;
Particle[i].SpeedX := sinAfParticle[i].Angle] * Speed;
Particle [i] . SpeedY := cosA[Particle [i] .Angle] * Speed;
Particle [i] . r := random (256); // Сине-красный цвет
Particle [i] . g := 0;
Particle[i] .b := random (256);
end;
end;
Первый раз система инициализируется в начале работы приложения. Здесь же заполняются вспомогательные массивы, хранящие синусы и косинусы углов:
sinA : Array [0..360] of Single;
cosA : Array [0..360] of Single;
PS : TParticleSystem;
for j := 0 to 360 do begin // Для оптимизации, чтобы вычислять
sinA[j] := sin(j * Pi / 180); // только один раз
cosA[j] := cos(j * Pi / 180); end;
PS := TParticleSystem. Create; // Создание системы
PS.Init (DEFAULT_SIZE, DEFAULT_POWER) ; // Инициализация системы
В методе calculate класса вспышки пересчитываются текущие координаты частиц:
procedure TParticleSystem. Calculate;
var
i : Integer;
begin
for i := 0 to Size do begin
if Particle [i] .Energy > 0 then begin
Particle [i] .X := Particle [i] .X + Particle [i]. SpeedX;
// Частицы отскакивают от границ экрана
if Particle [i] .X >= ScreenWidth - 1 then begin
Particle [i ] .SpeedX :="-0.5 * Particle [i]. SpeedX;
Particle [i] .X := ScreenWidth - 1;
end;
if Particle [i] .X < 0 then begin
Particle [i] .SpeedX := -0.5 * Particle [i]. SpeedX;
Particle [i] .X := 0;
end;
Particle [i].Y := Particle [i] .Y + Particle [i] . SpeedY;
if Particle [i] .Y >= ScreenHeight - 1 then begin
Particle [i] .SpeedY := -0.3 * Particle [i] . SpeedY;
Particle[i] .Y := ScreenHeight - 1;
end;
if Particle [i] .Y < 0 then begin
Particle [i] .SpeedY := -Particle [i] . SpeedY;
Particle[i].Y := 0;
end;
Particle[i].Energy := Particle[i].Energy - 1;
Particle[i].SpeedY := Particle[i].SpeedY + 0.2;
end;
end;
end;
Самый главный для нас метод - воспроизведение частиц системы:
function TParticleSystem.Render : HRESULT;
var
i : Integer;
desc : TDDSURFACEDESC2;
hRet : HRESULT;
begin
ZeroMemory (@desc, SizeOf(desc));
desc.dwSize := SizeOf(desc);
hRet := frmDD.FDDSBack.Lock (nil, desc, DDLOCKJSAIT, 0);
if Failed (hRet) then begin Result := hRet;
Exit;
end;
// Очистка экрана
ZeroMemory (desc.IpSurface,
desc.lPitch * ScreenHeight * (ScreenBitDepth div 8));
// Заполняем пикселы в соответствии с состоянием системы частиц
for i := 0 to Size do
if (Particle[i].Energy > 0) then
PWord (Integer(desc.IpSurface) +
trunc (Particle[i].Y) * desc.lPitch +
trunc (Particle[i].X) * (ScreenBitDepth div 8))^ :=
Particle[i].B or (Particle[i].G shl 5) or (Particle[i].R shl 11);
Result := frmDD.FDDSBack.Unlock(nil) ;
end;
При каждой перерисовке экрана отображается текущее состояние системы:
function TfrmDD.UpdateFraine : HRESULT;
var
hRet : HRESULT;
begin
Result := DD_FALSE;
PS.Calculate; // Пересчитываем положения частиц
// Воспроизведение состояния системы
hRet := PS.Render;
if Failed (hRet) then begin
Result := hRet;
Exit;
end;
Time := Time + 1; // Простейший эмулятор таймера
if Time > 15 then begin // Прошел срок существования системы
PS.Init(DEFAULT_SIZE, DEFAULT_POWER); // Вспышка в новом месте
Time := 0;
end;
Result := DD_OK;
end;
Полупрозрачность
Такой прием часто используется в играх. Автоматизации полупрозрачности DirectDraw не предоставляет, все необходимо делать самому разработчику, попикселно накладывая данные источника и приемника.
В общем случае формула вычисления значения цветовых компонентов выглядит так:
Result = Alpha * srcColor + (1 - Alpha) * destColor
Здесь Alpha - коэффициент прозрачности, принимающий вещественное значение в пределах от нуля до единицы; srcColor - цвет источника; destColor - цвет приемника.
Если Alpha равно нулю, то получаем цвет приемника; если Alpha имеет единичное значение, источник совершенно непрозрачен. Если мы имеем дело с образом, двигающимся по поверхности, то под источником подразумеваем образ, а фон считаем приемником.
Формулу можно оптимизировать. Начнем с того, что избавимся от присутствия двух операций умножения. Перестроим уравнение так, чтобы присутствовала лишь одна из них: