Графика DirectX в Delphi - Михаил Краснов
Шрифт:
Интервал:
Закладка:
Иллюстрацией служит проект каталога Ех09. Код обработчика onPaint формы дополнился локальной переменной ddsd2 типа TDDSurfaceDesc2, перед блиттингом с ней производятся обычные действия (обнуление всех полей и задание размера), используется она с целью хранения информации о параметрах поверхности, для получения которых и вызывается изучаемый метод:
//В ddsd2 занести данные о поверхности
FDDSImage.GetSurfaceDesc (ddsd2);
// Размеры srcRect устанавливаются равными размерам поверхности
SetRect (srcRect, 0, 0, ddsd2.dwWidth, ddsd2.dwHeight);
Сейчас в качестве упражнения рекомендую выполнить следующее задание: создайте простейшую программу просмотра bmp-файлов. После загрузки приложения пользователь выбирает нужный файл с помощью стандартного диалога. Растр выводится на полный экран.
Еще один простой пример по поводу блиттинга - проект каталога Ех10. Здесь экран раскрашивается подобно радуге (рис. 2.2).
Используется растр размером 1024x1, т. е. высотой в один пиксел. Не забывайте, что карты с небольшой видеопамятью не способны создать вторичную поверхность больше первичной. Некоторые читатели не смогут насладиться всей красотой этого примера, но ничего не потеряют, поскольку следующий проект выводит все тот же растр, и должен работать на всех картах.
В проекте каталога Ex11 я напоминаю о другом способе масштабирования растров, обычном для Delphi. При создании вторичной поверхности растровое изображение все также загружается в объект wrkBitmap. Затем создается вспомогательный объект wrkBitmapl, его ширина - 640 пикселов, высота - 1 пиксел. После чего "масштабируется" прежний растр и выводится на канве wrkBitmapi с помощью метода StretchDraw:
wrkBitmapi.Canvas.StretchDraw (Rect (0, 0, wrkBitmapi.Width,
wrkBitmapi.Height), wrkBitmap);
Размеры вторичной поверхности теперь должны опираться на размеры именно второго растра.
Такой способ масштабирования более эффективен. Задайте высоту растра равной 60 пикселам, и радуга должна заполнить экран гораздо быстрее, чем в двух предыдущих способах, поскольку меньше тратится времени при окончательном растяжении вторичной поверхности.
Упражнение: сделав wrkBitmapl глобальной переменной, добейтесь уверенного восстановления изображения.
Аналогичный прием со вспомогательным объектом класса TBitmap используется в очередном примере (проекте каталога Ех12), в котором образ загружается из jpg-файла, а при выводе картинка заключается в рамку (рис. 2.3).
В списке uses добавлены модули extctris и jpeg для использования динамически создаваемого объекта image класса Timage, в который будет загружаться jpg-файл :
Image := Timage.Create (nil); // Создаем объект
Image.Picture.LoadFromFile ('..lake.jpg'); // Загружаем jpg
// Непосредственно Image использовать не сможем
wrkBitmap := TBitmap.Create; // Вспомогательный Bitmap
wrkBitmap.Width := 640; // Размеры - все окно, чтобы не было искажений
wrkBitmap.Height := 480;
// Фон прямоугольника рамки // Рамка обрамляется красным // Толщина карандаша
wrkBitmap.Canvas.Brush.Color := clBlue;
wrkBitmap.Canvas.Pen.Color := clRed;
wrkBitmap.Canvas.Pen.Width := 5;
wrkBitmap.Canvas.Rectangle (150, 100, 490, 380); // Рамка
// Воспроизводим jpg на канве
wrkBitmap.Canvas.Draw (192, 112, Image.Picture.Graphic);
Image.Free; // Image больше не нужен
Канва в примере используется только при подготовке поверхности, посему мы не потеряем в скорости при воспроизведении.
Будьте внимательны, основной фон экрана в рассматриваемом примере - серый, поскольку за нашей картинкой выступает поверхность основного окна. Такое сочетание вывода функциями GDI и командами DirectDraw вообще-то надо избегать, заполняя весь фон вторичной поверхности. Если вы внимательно исследуете содержимое заголовочного файла DirectDraw.pas, то легко сможете обнаружить, что свойства блиттинга гораздо шире изученных нами. Например, поверхность можно вращать при выводе. Удобная возможность, но предоставляется только акселератором, причем далеко не каждым. Поэтому изучить вам это придется самостоятельно. А мы перейдем к другому методу поверхности, осуществляющему блиттинг - методу BitFast. Рассмотрим пример, представленный в проекте каталога Ех13. Картинка загружается из jpg-файла, внеэкранная поверхность должна закрывать собой весь экран:
wrkBitmap. Width := 640; // По размерам совпадает с устанавливаемым wrkBitmap. Height := 480; // экранным режимом
wrkBitmap. Canvas. Brush. Color := clBlack; // Фон экрана установим черным wrkBitmap. Canvas. Rectangle (0, 0, 640, 480); // Закрасим весь экран wrkBitmap. Canvas . Draw (192, 112, Image. Picture. Graphic ) ; // Вывод jpg
Воспроизведение основано на методе BitFast:
hRet := FDDSPrimary. BitFast (0, 0, FDDSImage, nil, DDBLTFAST_WAIT) ;
Первые два аргумента задают координаты (х, у) левого верхнего угла размещаемого блока в принимающей поверхности. Дальше указывается вставляемая поверхность. Предпоследний аргумент - величина типа TRECT - задает вырезаемую из вставляемой поверхности область. Точно так же, как и в случае с методом Bit, желательно явно задавать размеры, даже в случае, когда поверхность вставляется целиком. Последний аргумент определяет условия работы блиттинга. Пока мы задаем одиночное значение. Константа изменилась, но смысл ее использования аналогичен DDBLT_WAIT.
Метод BitFast более привлекателен в использовании и работает быстрее. Но он имеет некоторые ограничения в сравнении с методом Bit, например, не предоставляет возможности автоматического масштабирования, не может использоваться для заполнения фона так, как мы это делали раньше.
Буферы
Итак, будем стараться использовать метод BitFast всегда, когда это возможно, т. к. скорость работы приложения является для графики наиважнейшей характеристикой. Посмотрим, как выглядит с применением этого метода перерисовка в проекте каталога Ех14. С помощью клавиш перемещения курсора можно управлять положением картинки на экране по горизонтальной оси. Хотя скорость воспроизведения и увеличилась, но мельтешение полос при перерисовке картинки осталось. Экран мы заполняем в два этапа: вначале все закрашиваем черным цветом, затем накладываем картинку. В промежуток времени между этими действиями экран успевает обновиться, и мы видим эти раздражающие полосы.
Попробуем проделать следующее: создадим внеэкранную вспомогательную поверхность, по размерам равную первичной. Воспроизводить изображение будем на нее, а окончательную картинку перенесем на экранную поверхность. Иллюстрацией этого ловкого приема служит проект каталога Ех15.
Поскольку значения устанавливаемых параметров экрана в коде используются неоднократно, введем соответствующие константы:
ScreenWidth = 640;
ScreenHeight = 480; ScreenBitDepth = 16;
Для загрузки растра вызовем вспомогательную функцию DDLoadBitmap из модуля DDUtii, объединяющую создание поверхности, собственно загрузку и копирование растра на поверхность:
FDDSImage := DDLoadBitmap (FDD, szBitmap, 0, 0); // Укороченный код
if FDDSImage = nil then begin // Произошла ошибка
ErrorOut (hRet, ' DDLoadBitmap' ) ;
Exit
end;
Функция пытается загрузить растр из ресурсов, в случае неудачи загружает файл. Первым аргументом задается главный объект DirectDraw, затем имя файла - у нас это константа szBitmap, - дальше указываются требуемые размеры поверхности или нули, если поверхность должна иметь размеры растра.
Вспомогательная поверхность носит имя FDDSBack и в начале и конце работы приложения "обнуляется" самой первой. Ее размеры задаются равными размерам первичной поверхности:
FillChar (ddsd, SizeOf (ddsd) , 0) ; with ddsd do begin
dwSize := SizeOf (ddsd) ;
dwFlags := DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH;
ddsCaps . dwCaps := DDSCAPS_OFFSCREENPLAIN;
dwWidth := ScreenWidth;
dwHeight := ScreenHeight; end;
hRet := FDD. CreateSur face (ddsd, FDDSBack, nil); if hRet <> DD_OK then begin
ErrorOut (hRet, 'Create Back Surface');
Exit
end;
Перерисовка окна принципиально изменилась тем, что весь вывод осуществляется на вспомогательную поверхность, содержимое которой в законченном виде помещается на первичную:
ZeroMemory(@ddbltfx, SizeOf (ddbltfx) ) ; ddbltfx.dwSize := SizeOf (ddbltfx) ; ddbltfx. dwFillColor := 0;
while True do begin // Закрашиваем фон вторичной поверхности черным hRet := FDDSBack. Bit (nil, nil, nil, DDBLT COLORFILL or DDBLT WAIT, @ddbltfx) ;
// Внеэкранная поверхность также может быть потеряна
if hRet = DDERR^SURFACELOST then begin if Failed (RestoreAll) then Exit;
end
else Break; end;
// Помещаем растр на вспомогательную поверхность while True do begin
hRet := FDDSBack.BltFast (1ft, 112, FDDSImage, nil, DDBLTFAST_WAIT);
if hRet = DDERR_SURFACELOST then begin if Failed (RestoreAll) then Exit;
end
else Break; end;
// Вспомогательная поверхность заполнена, блиттинг производится //на первичную while True do begin
hRet := FDDSPrimary.BltFast (0, 0, FDDSBack, nil, DDBLTFAST_WAIT);
if hRet = DDERR_SURFACELOST then begin if Failed (RestoreAll) then Exit;
end
else Break;
end;
Функция восстановления поверхности использует вспомогательную функцию перезагрузки растра DDReLoadBitmap модуля DDUtil:
function TfrmDD.RestoreAll : HRESULT; begin
Result = DD_FALSE;
if Succeeded (FDDSPrimary._Restore) then begin if Failed (FDDSImage._Restore) then Exit;
// Рекомендуется перезагрузить растр после восстановления if Failed (DDReLoadBitmap(FDDSImage, szBitmap)) then Exit;
// Добавилось восстановление еще одной вспомогательной поверхности
if Failed (FDDSBack.^Restore) then Exit;