Михаил Краснов - Графика DirectX в Delphi
Черный цвет для использования его в качестве ключа подходит для этого фона, но пример будет некрасиво работать с фоном, подобным рис. 3.11, где присутствует масса участков именно черного цвета.
В проекте каталога Ех22 приведено другое решение задачи, менее элегантное, но работающее с любыми цветовыми ключами.
Здесь, помимо поверхности FDDSZoom, введена поверхность FDDSDouble. Для первой из них в качестве ключа взят чистый зеленый цвет, как отсутствующий на фоне. Вторая поверхность создается путем загрузки изображения-шаблона - зеленый квадрат с черным кругом посередине. Ключом для нее установлен черный цвет.
Теперь на поверхность лупы последовательно помещаем растянутый участок фона, затем ограничивающий шаблон:
SetRect (wrkRect, mouseX + 25, mouseY + 25, mouseX + 75, mouseY +- 75);
// Растягиваем участок фона
FDDSZoom.Blt (nil, FDDSBackGround, @wrkRect, DDBLT_WAIT, nil);
// Вместо черных участков шаблона останется увеличенный фрагмент
FDDSZoom.BltFast (О, О, FDDSDouble, nil,
DDBLTFASTJMAIT or DDBLTFAST^SRCCOLORKEY);
// Зеленая канва не воспроизведется FDDSBack.BltFast (mouseX, mouseY, FDDSZoom, nil,
DDBLTFAST_WAIT or DDBLTFAST_SRCCOLORKEY);
Позже мы вернемся к задаче с лупой и получим искаженное изображение в ее круге.
Палитры
Для хранения отдельного набора цветовых составляющих палитры используется переменная типа TPaietteEntry. Переменная типа iDirectDrawPaiette, как мы знаем из предыдущих примеров, служит для установления определенной палитры 8-битной поверхности. Обычно такая палитра загружается из растра.
В любой момент времени мы можем получить набор палитры и модифицировать его, как это делается в следующем нашем примере (проект каталога Ех23). За основу взят проект с перемещающимся драконом, но здесь с течением времени экран становится тусклым, имитируется суточная смена освещенности. Дойдя до некоторой фазы, восстанавливается первоначальная яркость. Такой эффект постепенного угасания называется fade (затухание). Разберем, как он создается.
Для хранения первоначальной палитры предназначен массив:
DefPal : Array[0..255] of TPaietteEntry;
Массив заполняется после загрузки палитры из растра, для чего вызывается
Метод Палитры GetEntries:
hRet := FDDpal.GetEntries(0, 0, 256, @DefPal);
if Failed (hRet) then ErrorOut(hRet, 'Palette GetEntries');
При каждом перемещении образа во всех составляющих текущей палитры убавляются веса цветов, используется локальный массив palEntries:
// Получаем составляющие текущей палитры экрана FDDpal.GetEntries(О, О, 256, @PalEntries) ;
for i := 0 to 255 do begin // Цикл по всем элементам палитры
if PalEntries[i].peRed > Step then PalEntries[i].peRed :=
PalEntries[i].peRed - Step;
if PalEntries[i].peGreen > Step then PalEntries[i].peGreen :=
PalEntries [i] .peGreen - Step
if PalEntries[i].peBlue > Step then PalEntries[i].peBlue :=
PalEntries[i].peBlue - Step;
end;
// Устанавливаем текущей палитру, образованную элементами массива
FDDPal.SetEntries(0, 0, 256, @PalEntries);
Timer := (Timer + 1) mod 100;
// Восстанавливаем первоначальную палитру
if Timer = 0 then FDDpal.SetEntries(0, 0, 256, @DefPal);
Эффект угасания часто применяется для необычного завершения работы приложения.
Модификация палитры может использоваться также для создания эффекта цветовой анимации. Для этого различные участки поверхности рисуются в индивидуальных цветах, а при поочередном затемнении некоторых цветовых наборов палитры создается эффект перемещения, на экране последовательно появляются отдельные образы.
Рассмотрим простейший пример на эту тему - проект каталога Ех24. Фон представляет собой рисунок, построенный серией эллипсов, нарисованных оттенками серого; цвета повторяются в каждой серии (рис. 3.12).
Равномерно удаленные компоненты палитры с течением времени последовательно заменяются желтоватым цветом, остальные элементы ее затемняются. На экране по очереди появляются близко расположенные окружности и возникает иллюзия их движения.
Целочисленная переменная kr задает текущую незатемняемую палитру и изменяется от шестнадцати до двух, уменьшаясь на каждом шаге:
function TfrmDD.UpdateFrame : HRESULT;
var
k : Integer;
DefPal : Array[0..255] of TPaletteEntry; // Массив цветов палитры
hRet : HRESULT;
begin
ThisTickCount := GetTickCount;
if ThisTickCount - LastTickCount > 10 then begin
// Берем текущую палитру
hRet := FDDPal.GetEntries(0, 0, 256, SDefPal);
if Failed (hRet) then begin Result := hRet;
Exit;
end;
for k := 0 to 14 do begin // Затемняем предыдущий цвет палитры
DefPal [kr * 15 + k].peBlue := 0;
DefPal [kr * 15 + k].peRed := 0;
DefPal [kr * 15 + k].peGreen := 0;
end;
Dec (kr); // Переходим к следующему цвету палитры
if kr < 2 then kr := 16;
for k := 0 to 14 do begin // Подменяем текущий цвет желтоватым
DefPal [kr * 15 + k].peBlue := 0;
DefPal [kr * 15 + k].peRed :== 128;
DefPal [kr * 15 + k].peGreen := 100;
end;
// Устанавливаем измененную палитру
hRet := FDDPal.SetEntries(0, 0, 256, @DefPal);
if Failed (hRet) then begin Result := hRet;
Exit;
end;
LastTickCount := GetTickCount;
hRet := FDDSPrimary.Flip(nil, DDFLIP_WAIT);
if Failed (hRet) then begin Result := hRet;
Exit;
end;
end;
Result := DD_OK;
end;
Обратите внимание, что при запуске и восстановлении приложения появляется первоначальная фоновая картинка, поскольку затеняются "ненужные" цвета палитры только после первого прохождения значения kr по кругу.
Оконные приложения
Вы, должно быть, уже привыкли к тому, что наши примеры работают в полноэкранном режиме. Обычно оконные приложения создаются в DirectDraw только в случае крайней необходимости, т. к. слишком многое мы теряем при его использовании. Главная потеря здесь - скорость работы приложения.
Скорость воспроизведения, в общем случае, выше у полноэкранных приложений. Конечно же, при отображении небольшого числа блоков в маленьком окне вы сможете добиться очень высокой скорости.
Для оконных приложений нельзя использовать переключение страниц или двойную буферизацию.
По своей сути, оконные приложения похожи на наши самые первые примеры, с выводом кружка на поверхности окна. Точно так же первичной поверхностью является весь рабочий стол экрана, и приложение должно отслеживать положение окна.
Рассмотрим простейший пример, располагающийся в каталоге Ех25. Работа его совсем проста, в пределах окна выводится хорошо нам знакомый растр с горным пейзажем.
Свойство Borderstyle формы приняло теперь свое обычное значение bssizeabie, удалены единственный компонент и все, связанное с курсором. Не можем мы также здесь задавать параметры экрана и устанавливать исключительный уровень кооперации, поскольку для оконных приложений задается обычный уровень доступа:
hRet := FDD.SetCooperativeLevel(Handle, DDSCL_NORMAL);
Появился обработчик перерисовки окна, в котором определяем текущее положение окна приложения и выводим на него масштабированный растр:
procedure TfrmDD.FormPaint(Sender: TObject);
var
rcDest : TRECT;
p : TPOINT; // Вспомогательная точка для определения положения окна begin
р.Х := 0;
p.Y := 0;
// Находим положение на экране точки левого верхнего угла
// клиентской части окна приложения
Windows.ClientToScreen(Handle, p);
// Получаем прямоугольник размерами клиентской части окна
Windows.GetClientRect(Handle, rcDest);
OffsetRect(rcDest, p.X, p.Y); // Сдвигаем прямоугольник на р.Х, p.Y
if Failed (FDDSPrimary.Blt (@rcDest, FDDSBackGround, nil,
DDBLT_WAIT, nil)) // Выводим растр then RestoreAll;
end;
Перед именами некоторых процедур я указал, что имеются в виду процедуры именно модуля windows. Если для вас привычнее использовать аналогичные методы формы, то эти строки можете записать так:
р := ClientToScreen(р); rcDest := GetClientRect;
Хоть в рассматриваемом примере и приходится следовать рекомендациям разработчиков, пытаясь восстанавливать все поверхности в случае неудачи при воспроизведении, но сделано это большей частью формально. Если по ходу приложения поменять установки экрана, то оно не сумеет восстановить первичную поверхность. Это не является недостатком конкретно нашего примера. Точно так же ведут себя все оконные приложения, использующие DirectDraw.
Если вы внимательно посмотрите на работу приложения, то должны заметить, как плохо масштабируется картинка при изменении размеров окна. Для более удовлетворительной работы обработчик этого события должен вызывать код перерисовки окна.
Оконное приложение может рисовать в любом месте рабочего стола. Малейшая ошибка в коде приведет к очень некрасивым результатам. Обычно такие приложения ограничивают область вывода, для чего используется объект класса IDirectDrawClipper.
Посмотрим проект каталога Ех2б, в коде модуля которого появилась переменная FDDCiipper такого типа. В начале и конце работы приложения ее значение, как принято, устанавливается в nil.
Сразу после создания первичной поверхности формируется этот объект, ответственный за отсечение области вывода, и присоединяется к области вывода:
// Создание объекта отсечения
hRet := FDD.CreateClipper(0, FDDCiipper, nil);
if Failed (hRet) then ErrorOut(hRet, 'CreateClipper FAILED');
// Определяем окно, связанное с отсечением области вывода
hRet := FDDCiipper.SetHWnd(0, Handle);
if Failed (hRet) then ErrorOut(hRet, 'SetHWnd FAILED');