Михаил Краснов - Графика DirectX в Delphi
for Index := 0 to MaxParticles do
with Particle [Index] do begin
Speed := 1 + round (random (3)) ;
Angle : = random * 2 * Pi;
X := random (ScreenWidth - 1) + 1;
Y := random (ScreenHeight - 1) + 1;
Decay := random;
HalfLife := random / 20;
AngleAdjustment := random / 20;
end;
При каждом обновлении экрана отслеживаются новые позиции частиц и усредняются цвета пикселов, подобно предыдущему примеру:
for Index := 0 to ParticleCount do
with Particle [Index] do begin
Decay := Decay - HalfLife; // Уменьшить время жизни
// Срок существования прошел, появляется новая точка
if Decay <= 0 then begin
Decay := 1;
X := mouseX; // В позиции курсора
Y := mouseY;
end;
Angle := Angle + AngleAdjustment; // Движение по спирали
If Angle >= 2 * Pi then Angle := 0; //От переполнения
X := X + round (cos(Angle) * Speed); // Новая позиция
Y := Y + round (sin(Angle) * Speed);
// Точка, ушедшая за границу экрана
if (X > ScreenWidth - 2) or (X < 2) then begin
X := mouseX; // Переместить в позицию курсора
Y : = mouseY;
Angle := random * 2 * Pi;
end
else if (Y > ScreenHeight - 2) or (Y < 2) then begin
X := mouseX;
Y := mouseY;
Angle := random '* 2 * Pi;
end;
// "Отображение" точки
Pict [X, Y] := Speed * 16 + 186;
end;
// Эффект размытости for Index := 1 to BlurFactor do for X := 2 to ScreenWidth - 2 do
for Y := 2 to (ScreenHeight - 2) do begin
// Усреднение значения девяти соседних элементов Accum := 0;
Accum := Accum + Pict [X, Y] +
Pict[X, Y + 1] + Pict[X, Y - 1] +
Pict[X + 1, Y] + Pict[X - 1, Y] +
Pict[X + 1, Y + 1] + Pict[X - 1, Y - 1] +
Pict[X + 1, Y - 1] + Pict[X - 1, Y + 1];
Accum := Accum div 9; // Усреднение значений
// соседних пикселов
Pict [X, Y] :=' Accum;
end;
Чтобы изображение не съеживалось с течением времени, как в предыдущем примере, закрашиваясь черным цветом, граничные точки экрана заполняются ненулевыми значениями:
for Index := 0 to ScreenWidth - 1 do begin
Pict[Index, 0] := 127;
Pict[Index, ScreenHeight - 1] := 127;
Pict[Index, 1] := 127;
Pict[Index, ScreenHeight - 2] := 127;
end;
for Index := 0 to ScreenHeight - 1 do begin
PictfO, Index] := 127;
Pict[ScreenWidth - 1, Index] := 127;
Pict[l, Index] := 127;
Pict[ScreenWidth - 2, Index] := 127;
end;
С помощью клавиш <Ноте> и <End> можно менять количество частиц, а с помощью клавиш <Page Up> и <Page Down> - управлять степенью усреднения пикселов.
Пример может работать при разных разрешениях и глубине цвета экрана. Обратите внимание, что при его очистке размер блока в таких случаях задается исходя из значения текущей глубины:
ZeroMemory (desc. IpSurface, desc.lPitch * ScreenHeight * (ScreenBitDepth div 8) ) ;
Также здесь нельзя использовать значение ширины экрана вместо lPitch, т. к. из-за выравнивания памяти это могут быть разные значения. Ширина поверхности "подгоняется" к границам параграфов, т. е. должна быть кратна 4-м байтам.
Массивы в видеопамять приходится переносить медленным способом - поэлементно. Одна ячейка массива занимает байт, при разрешении экрана в 16 разрядов на пиксел массив скопируется только в первую половину памяти поверхности. Если же вы в своем приложении не собираетесь менять разрешение, то вполне можете копировать массив целиком, одной командой CopyMemory.
Поскольку значения в массиве pict лежат в пределах диапазона типа Byte, то для 16-битного режима картинка получится не очень выразительной и отображается оттенками одного цвета.
Сохранение растровых изображений
Наверняка перед вами рано или поздно встанет задача сохранения получающихся картинок. Если вы попытаетесь их скопировать в буфер обмена для дальнейшей вставки в рисунок графического редактора, то обнаружите проблему с 256-цветными приложениями. Картинки будут искажаться, поскольку палитра таких изображений будет отличной от палитры рисунка.
Я приведу простейшее решение проблемы, основанное на использовании объекта класса TBitmap. В предыдущем примере обработчик формы нажатия клавиши приведите к следующему виду:
procedure TfrmDD. FormKeyDown (Sender: TObject; var Key: Word
Shift: TShiftState) ; var
BitMap : TBitmap; // Для записи картинок в файл begin
case Key of
VK NEXT : BlurFactor := BlurFactor + 1;
VK_PRIOR : begin
BlurFactor := BlurFactor - 1;
if BlurFactor < 1 then BlurFactor := 1;
end;
VK_HOME : begin
Inc (ParticleCount, 1000);
if ParticleCount > MaxParticles then ParticleCount := MaxParticles;
end;
VK_END : begin
Dec {ParticleCount, 1000);
if ParticleCount < 2000 then ParticleCount := 2000;
end;
// По нажатию пробела содержимое экрана сохраняется в файле
VK_SPACE : begin
BitMap := TBitmap.Create;
BitMap.PixelFormat := pf24bit; // Разрядность задаем 24
BitMap.Height := ClientHeight;
BitMap.Width := ClientWidth;
// Копируем в BitMap содержимое экрана
BitBlt(BitMap.Canvas.Handle, 0, 0, ClientWidth, ClientHeight,
Canvas.Handle, 0, 0, SRCCOPY);
BitMap.SaveToFile ('l.bmp'); // Записываем в файл
end;
VK_ESCAPE,
VK_F12 : Close;
end;
end;
Записываются 24-битные файлы, и информация о цвете не теряется в любом случае.
Доступ к пикселам в 16-битном режиме
В таком режиме информация о цвете пиксела разделяется на три цветовые составляющие, но шестнадцать на три нацело не делится, поэтому разработчики вынуждены прибегать к неравномерному распределению. Наиболее распространенной является схема 5-6-5. В этом формате первые пять битов хранят значение красного оттенка, следующие шесть битов отводятся под зеленую составляющую, ну и последние пять битов заняты оттенком синего. Всего получается 65 536 (216) различных цветов. Из них по 32 градации красного и синего, 64 градации зеленого.
Схема 5-6-5 является самой распространенной. Поэтому для начала будем опираться именно на нее. Как быть в случае другого формата, рассмотрим позднее.
Для примера возьмем цвет, образованный следующими значениями составляющих:
* красный, 5 бит: 00011; зеленый, 6 бит: 001011; синий, 5 бит: 00101.
Значение пиксела с таким цветом будет следующим (пробелы вставлены для удобочитаемости):
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