Поворот изображения
Такая эффектная операция, как я уже говорил, аппаратно поддерживается далеко не каждой видеокартой. Посмотрим, как можно использовать пикселные операции для осуществления поворота изображения (проект каталога Ех13). На экране вращается жуткое изображение (рис. 3.5).
![](image/3-5.jpg)
Рис. 3.5. Очень страшный пример поворота растра
Не пугайтесь, хоть картинка и страшная, сам пример совершенно безобиден, если только вы не будете лицезреть его работу чересчур долго.
Используется картинка размером 256x256 пикселов, для работы с которыми введен пользовательский тип:
type
TByteArray = Array [0..255, 0..255] of Byte;
Переменная Pict данного типа хранит растровое изображение, а массив заполняется в пользовательской процедуре, вызываемой в начале работы приложения и при каждом восстановлении поверхностей:
function TfrmDD.Prepare : HRESULT; var
desc : TDDSURFACEDESC2;
i, j : Integer;
hRet : HRESULT;
begin
hRet := Clear; // Очистка первичной поверхности
if Failed (hRet) then begin Result := hRet;
Exit;
end;
// Посередине экрана выводится картинка с черепом hRet := FDDSPrimary.BltFast (193, 113, FDDSImage, nil,
DDBLTFAST_WAIT or DDBLTFAST_SRCCOLORKEY);
if Failed (hRet) then begin Result := hRet; Exit;
end;
ZeroMemory (@desc, SizeOf(desc));
desc.dwSize := SizeOf(desc);
// Запираем поверхность
hRet := FDDSPrimary.Lock (nil, desc, DDLOCK_WAIT, 0);
if Failed (hRet) then begin
Result := hRet;
Exit;
end;
// Считываем в массив Pict содержимое нужных пикселов экрана for i := 0 to 255 do
for j := 0 to 255 do
Pict [i, j] := PBYTE (Integer (desc.IpSurface) +
(j + 113) * desc.lPitch + (i + 193)); Result := FDDSPrimary.Unlock (nil);
end;
Заполнить массив можно многими разными способами, например напрямую из растра. Также обращаю внимание, что массив можно заполнять и из содержимого поверхности FDDSImage, без промежуточного блиттинга на первичную. Если ключом является не черный цвет, следует анализировать цвет каждого пиксела и отбрасывать пиксел с цветом ключа, а при использовании черного цвета в качестве ключа можно просто копировать значения пикселов в массив. Так мы будем поступать в последующих примерах.
Переменная Angle хранит текущее значение угла поворота растрового изображения в радианах. Изменяется ее значение при обновлении окна через некоторый промежуток времени:
function TfrmDD.UpdateFrame : HRESULT; var
hRet : HRESULT; begin
Result := DD FALSE;
ThisTickCount := GetTickCount;
if ThisTickCount - LastTickCount > 30 then begin
Angle := Angle +0.1; // Угол в радианах
// Надо уберечься от переполнения
if Angle > 2 * Pi then Angle := Angle - 2 * Pi;
while True do begin
if Failed (Rotating) then begin // Поворот на Angle
hRet := RestoreAll;
if Failed (hRet) then begin // Неустранимая ошибка Result := hRet; Exit; end
end else Break end;
LastTickCount := GetTickCount; end;
Result := DD_OK; end;
Пользовательская функция Rotating, несмотря на свое название, не содержит кода самого поворота картинки, а лишь заменяет содержимое части экрана:
function TfrmDD.Rotating : HRESULT;
var
desc : TDDSURFACEDESC2;
i, j : Byte;
Image : TByteArray;
hRet : HRESULT;
begin
ZeroMemory (@desc, SizeOf(desc));
desc.dwSize := SizeOf(desc); // Получаем растр из первоначального путем
// поворота на угол Alpha относительно середины растра
Image := Rotate (Pict, 127, 127, Angle);
hRet := FDDSPrimary.Lock (nil, desc, DDLOCK_WAIT, 0);
if Failed (hRet) then begin Result := hRet;
Exit;
end;
// Заполняем блок экрана новым растром for i := 0 to 255 do
for j := 0 to 255 do
PByte (Integer (desc.IpSurface) + (j + 113) * desc.lPitch +
i + 193)Л := Image [i, j]; Result := FDDSPrimary.Unlock (nil);
end;
Самая интересная функция примера - пользовательская функция, возвращающая растр, повернутый на заданный угол относительно указанной точки:
function TfrmDD.Rotate (const pictOriginal : TByteArray; // Исходный растр
// Точка в растре, задающая оси поворота
const iRotationAxis, jRotationAxis: Integer;
const ug : Single): TByteArray; // Угол, радианы
type // Тип, соответствующий одной строке массива
wrkByteArray = Array [0..255] of Byte;
var
i, j :Integer;
iOriginal: Integer;
iPrime: Integer;
jOriginal: Integer;
jPrime: Integer;
RowOriginal :^wrkByteArray;
RowRotated :^wrkByteArray;
sinTheta :Single;
cosTheta :Single;
begin
sinTheta := sin(ug); // Для оптимизации синусы и косинусы
cosTheta := cos(ug); // Запоминаем в рабочих переменных
for j := 255 downto 0 do begin // Строки результирующего массива
RowRotated := @result [j, 0]; // Указатель на очередную строку
jPrime := j - jRotationAxis; // Смещение от оси по Y
for i := 255 downto 0 do begin // Цикл по столбцам
iPrime := i - iRotationAxis; // Смещение от оси по X
iOriginal := iRotationAxis + trunc(iPrime * cosTheta -
jPrime * sinTheta); // Координаты нужной точки по X
jOriginal := JRotationAxis + trunc(iPrime * sinTheta +
jPrime * cosTheta); // Координаты нужной точки по Y
// После поворота некоторые точки на границе
//не имеют аналога в старом растре
if (iOriginal >= 0) and (iOriginal <= 255) and // He границы
(jOriginal >= 0) and (jOriginal <= 255) then begin
// Копируем в новый растр точку RowOriginal := SpictOriginal[jOriginal, 0];
RowRotated'^ [i] := RowOriginal^[iOriginal]
end
else RowRotated[i] := 0; // Границы заполняем черным цветом
end
end;
end;
В этом и следующем примерах я не применяю двойную буферизацию. Если же с использованием вашей видеокарты по этой причине шоу разворачивается слишком медленно, в качестве упражнения установите двойную буферизацию.