Главная страничкаИсходники программСтатьи и описанияПроекты и разработкаПолезные ссылкиЗадай вопрос, получи ответВыскажи свое мнениеПишите письма

Спрайты и их масштабирование

Что такое спрайт я думаю, знают все, ну а те, кто не знает, напомню, это прямоугольная часть изображения, которая храниться в памяти и может быть выведена на экран в любом месте. А вот как это делается, я попытаюсь описать ниже.

Сначала опишем тип спрайта в блоке TYPE

TYPE

TSprite = Record
XRES,YRES:Word;
Data:Pointer;

End;

где

XRES - ширина спрайта
YRES - высота спрайта
DATA - указатель на место в памяти где храниться спрайт

Можно конечно сделать спрайт в виде массива, но тогда мы не сможем создавать спрайты разного размера, а это не очень хорошо. Хотя ограничения будут и здесь, размер данных спрайта в памяти не должен превышать 64Кб. Поэтому создадим некую переменную, скажем SPRITE_ERROR типа BOOLEAN которая будет нам говорить об ошибке создания спрайта.

VAR SPRITE_ERROR:Boolean;

Этой переменной мы можем пользоваться всегда, когда мы хотим быть уверенными в том, что спрайт создан верно.

Теперь описав тип спрайта, посмотрим, что нам нужно с ним делать. Для работы со спрайтами нам понадобиться:

Прозрачность спрайтов

Спрайт это прямоугольная область и вывод всех точек спрайта приведет к некоторым нежелательным результатам, т.е. если у вас есть задний фон то либо изображение спрайта должно быть таким же квадратным как спрайт, либо пустые точки закрасят фон. В палитровых (256 цветов) режимах прозрачность делается следующим образом, один цвет палитры просто не рисуется, он и является прозрачностью (зачастую это нулевой цвет). В не палитровых режимах (True Color и Hi Color) для этого дела выделяется бит, который указывает на то, что точку не рисовать. Хорошо для этого дела подходят 15 Битные видео режимы, там на каждый компонент цвета уходит по 5 бит, и 16 бит остается неиспользованным, его мы и используем для канала прозрачности.

Да еще, выше речь идет о простой прозрачности спрайта, то есть либо ставить точку, либо нет. Если у нас появилась надобность рисовать полупрозрачный спрайт то нам придется выделить еще один байт, в котором мы укажем, насколько прозрачным является данный пиксель спрайта.

Резервирование памяти под спрайт
Выше мы решили, что наш спрайт храниться в динамической памяти и размеры его будут ограничиваться только размером одного сегмента памяти компьютера (не более 64Кб). Для резерва памяти под спрайт напишем следующую процедуру.

Procedure NewSprite(Xres,Yres:Word;Var Sp:TSprite);
Begin

{Проверяем на выход размера спрайта за приделы сегмента}
If Xres * Yres <= $FFFF Then Begin

{Задаем ширину и высоту спрайта}
Sp.Xres:=Xres;
Sp.Yres:=Xres;
{Выделяем динамическую память}
GetMem(Sp.Data,Sp.Xres*Sp.Yres);
SpriteError:=False; {говорим что ошибки не было}

End Else SpriteError:=True; {иначе спрайт не создан}

End;

Этот пример прекрасно подходит для палитровых режимов (256 цветов) т.к. на одну точку у нас уходит один байт. Для режима Hi Color нам нужно будет на одну точку отводить два байта, а в режимах True Color 3 байта плюс прозрачность (4 байта). Поэтому нам под спрайт нам нужно будет выделять память по следующей формуле Xres*Yres*N, где N - количество байт на точку.

Для 15 битных режимов это будет выглядеть так:

Procedure NewSprite(Xres,Yres:Word;Var Sp:TSprite);
Begin

{Проверяем на выход размера спрайта за приделы сегмента}
If Xres * Yres*2 <= $FFFF Then Begin

{Задаем ширину и высоту спрайта}
Sp.Xres:=Xres;
Sp.Yres:=Xres;
{Выделяем динамическую память}
GetMem(Sp.Data,Sp.Xres*Sp.Yres*2);
SpriteError:=False; {говорим что ошибки не было}

End Else SpriteError:=True; {иначе спрайт не создан}

End;

К сожалению, спрайт получается в два раза меньше, зато работает. Хотя вы все еще спокойно можете создать спрайт размером 127x255. А вот в True Color'е максимальный спрайт может быть 128x128. Решить эту проблему можно следующим образом, используя карту цветов, то есть грузим палитру 256 цветового режима в память, и точки в спрайте используем как индексы в палитре, теперь спрайт может быть размером как в палитровых режимах, но появляется ограничение по цвету, хотя я думаю нам это не существенно. Тогда создаем спрайт по старой схеме, но не забываем подгружать цветовую карту.

Освобождение памяти занятой спрайтом.
Зачем это, я думаю не стоит объяснять, а делается это просто.

Procedure DelSprite(Var Sp:TSprite);
Begin

Sp.Xres:=0;
Sp.Yres:=0;
FreeMem(Sp.Data,Sp.Xres*Sp.Yres*2);

End;

Получение спрайта с экрана.
Получить спрайт с экрана можно двумя способами. Первый работает относительно медленно, потому что используем цикл FOR и каждый раз производит умножение для каждой точки. Другой более длинный, зато более быстрый.

Вариант 1й(простой)

Procedure GetSprite(X,Y:Integer;Sp:TSprite);
Var I,J:Integer;
Begin

For J:=0 to Sp.Yres-1 do
For I:=0 to Sp.Xres-1 do

Mem[Seg(Sp.Data^):Ofs(Sp.Data^)+J*Sp.Xres+I]:=GetPix(I+X,J+Y);

End;

Вариант 2й(оптимизированный)

Procedure GetSprite(X,Y:Inetger;Sp:TSprite);
Var I,J:Integer;
Col:Byte;
P:^Byte;
W:^Word;
Begin

{обнуляем счетчики}
I:=0;
J:=0;
{делаем дублирующий указатель на Sp.Data при этом как указатель на байт, заодно смотрим а есть ли мальчик (спрайт в памяти), че брать, если некуда положить}
If Sp.Data = Nil Then Exit Else P:=Sp.Data;
{Делаем указатель на сам указатель, при этом указываем на него как на тип WORD. Теперь мы можем смело увеличивать смещение указателя P}
W:=@P;
Repeat

{получаем точку}
P^:=GetPix(i+X,j+Y);
Inc(W^); {Переходим на следующую точку в спрайте}
{пересчет шагов}
Inc(I);
If I=Sp.Xres then begin

I:=0;
Inc(J);

End;

Until J=Sp.Xres;

End;

Вывод спрайта
Вывод такой же, как и получение спрайта, разница в том, что вместо получения точки, мы ее ставим.

Приведем теже два примера.
Вариант 1й(простой)

Procedure PutSprite(X,Y:Integer;Sp:TSprite);
Var I,J:Integer;
Begin

For J:=0 to Sp.Yres-1 do

For I:=0 to Sp.Xres-1 do

{Вот и вся разница}
If Mem[Seg(Sp.Data^):Ofs(Sp.Data^)+J*Sp.Xres+I] <> 0 Then
Pix(I+X,J+Y,Mem[Seg(Sp.Data^):Ofs(Sp.Data^)+J*Sp.Xres+I]);

End;

Вариант 2й(оптимизированный)

Procedure PutSprite(X,Y:Inetger;Sp:TSprite);
Var I,J:Integer;
Col:Byte;
P:^Byte;
W:^Word;
Begin

I:=0;
J:=0;
If Sp.Data = Nil Then Exit
Else P:=Sp.Data;
W:=@P;
Repeat

{Ставим точку}
If P^<>0 then Pix(i+X,j+Y,P^); {<<< Вот и вся разница}
Inc(W^);
Inc(I);
If I=Sp.Xres then begin

I:=0;
Inc(J);

End;

Until J=Sp.Xres;

End;

Сохранение и загрузка спрайта в файл
Тут стоит подумать о будущем, то есть введем некоторое понятие формата файла, где сначала будет идти заголовок, а потом данные. В заголовке мы сохраним следующие вещи, Type - 1 байт, Xres, Yres - по два байта, ну и в поле данных поместим сам спрайт. А сделаем это так.

Параметры ф-ции.
Fname - имя файла
Fofs - смещение от начала файла (для простых спрайтов 0)
Sp - сам спрайт

Function SaveSprite(Fname:String;Fofs:LongInt;Sp:TSprite):LongInt;
Var F:File;
Tp:byte;
Begin

{смотрим а есть ли у нас что записать}
If Sp.Data = Nil Then Begin

SpriteError:=True;
SaveSprite:=Fofs;
Exit;

End Else SpriteError:=False;
Assign(F,Fname);
{смотрим, а вдруг файл у нас уже есть}
{$I-}Reset(F,1);{$I+}
{если нет, то создаем новый}
If IoResult <> 0 then Rewrite(F,1);
{смещаемся на нужное место}
Seek(Fofs);
Tp:=1;
{Задаем тип спрайта, предположим 1 это спрайт для палитровых режимов}
BlockWrite(F,Tp,1);
{Пишем ширину и высоту}
BlockWrite(F,Sp.Xres,2);
BlockWrite(F,Sp.Yres,2);
BlockWrite(F,Sp.Data^,Sp.Xres*Sp.Yres);
{текущее положение нам может пригодиться}
SaveSprite:=FilePos(F);
{Закрываем спрайт}
Close(F)

End;

Теперь дальше, загрузка спрайта

Function LoadSprite(Fname:String;Fofs:LongInt;Var Sp:TSprite):LongInt;
Var F:File;
Tp:byte;
Begin

Assign(F,Fname);
{открываем файл и смотрим, а есть ли он у нас }
{$I-}Reset(F,1);{$I+}
If IoResult = 0 then Begin

{смещаемся на нужное место}
Seek(Fofs);
{проверяем тип спрайта}
BlockRead(F,Tp,1);
If Tp<>1 Then Begin

SpriteError:=True;
Exit;

End;
{читаем ширину и высоту}
BlockRead(F,Sp.Xres,2);
BlockRead(F,Sp.Yres,2);
{создаем новый спрайт}
NewSprite(Sp.Xres,Sp.Yres,Sp);
{читаем данные}
BlockRead(F,Sp.Data^,Sp.Xres*Sp.Yres);
{текущее положение нам может пригодиться}
LoadSprite:=FilePos(F);
{Закрываем спрайт}
Close(F);
SpriteRrror:=False;
End Else Begin
{если файл не найден}
SpriteError:=True;
LoadSprite:=Fofs;

End;

End;

Вот и все, правда есть один маленький баг, запись спрайта не удаляет файл если он есть, но это я сделал нарочно, для того, чтобы в последствии можно было создавать библиотеки спрайтов. Для создания спрайтов также была введена переменная Fofs, которая задает смещение в файле, и также обе функции я описал в виде Function(…):LongInt а не в виде Procedure. Идея тут в следующем. Предположим записав спрайт в файл, нам нужно записать за ним еще один, тут и срабатывают все хитроумные свойства. А делается это так.

{Предположим у нас есть 10 спрайтов}
Var Spr : Array [1..10] of TSprite;
SS : LongInt;
Begin

...
{Предположим мы получили спрайты с экрана, и нам нужно их все сохранить в один файл, делаем цикл от 1 до 10}
SS:=0;
For i:=1 to 10 do

{сохранили I-тый спрайт, а SS получает новое смещение, как раз за спрайтом}
SS:=SaveSprite('Sp.dat',SS,Spr[i]);

End;

Опа как !!!

Масштабирование спрайта (растягивание)
Ну вот и последняя часть сказочки. Мы умеем брать, ставить, сохранять, грузить и т.д. и т.п. Теперь начинаются извращения, нам нужно натянуть спрайт на весь экран. Как это сделать? А просто.

Описывать в лом (пальцы устали) скажу кратко, находим отношение между сторонами спрайта и сторонами новой области, идем не по спрайту, а по области и находим нужные точки в спрайте, если сделать на оборот то получатся дырки. Вообще, по моему, это в школе проходят в той или иной степени толи по геометрии, толи по алгебре.

Вот вам процедура вывода натянутого спрайта.
P.S. ни какой билинейной фильтрации.

Procedure SPutImage(X,Y,X1,Y1:Integer;Im:TImage);
Var i,j:Integer;
SX,SY,XC,YC:Real;
YP,XR,YR:Integer;
A:Byte;
SG,FS:Word;
Begin

{смотрим, а нам это надо}
If Sp.Data=Nil then Exit;
{смотрим на кривость введенных координат}
If X>X1 Then Begin I:=X;X:=X1;X1:=I End Else If X=X1 then X1:=X+1;
If Y>Y1 Then Begin I:=Y;Y:=Y1;Y1:=I End Else If Y=Y1 then Y1:=Y+1;
{находим ширину и высоту нашей области}
XR:=X1-X;YR:=Y1-Y;
{находим отношения сторон спрайта, к сторонам области}
XC:=(Im.Xres-1) / XR;
YC:=(Im.Yres-1) / YR;
{обнуляем счетчик}
SX:=0;SY:=0;
{берем адрес спрайта}
SG:=Seg(Im.Data^);
FS:=Ofs(Im.Data^);
{поехали сначало по Y}
For j:=0 to YR do Begin

{находим Y, и все для него (пытаемся оптимизировать)}
YP:=FS+Round(SY) * Im.Xres;
{смещение в спрайте}
SX:=YP;
{теперь цикл по X}
For i:=0 to XR do Begin

{взяли нужный цвет}
A:=Mem[SG:Round(SX)];
{поставили точку, если она не прозрачная}
If A>0 then Pix(i+X,j+Y,A);
{сместились в спрайте на отношение по X}
SX:=SX+XC;

End;
{сместились по Y на отношение по Y}
SY:=SY+YC

End;

End;

P.S. по алгебре у меня всегда была 3, поэтому если я что-то неправильно описал, то извините, а лучше напишите, ну а самое интересное то, что до этой процедуры я дошел сам. (а вот по Русскому у меня было угадайте что :) <4>).


Rambler's Top100