Программирование игр для Windows. Советы профессионала

       

Масштабирование растровых изображений


Люди обычно говорят: «А вот это мы оставим на сладкое...». В нашем случае мы оставили на сладкое самое сложное, что есть в этой главе. Я пытался свести всю математическую часть к минимуму, зная «любовь» программистов к тригонометрическим функциям и умножению. К сожалению, трехмерные игры содержат довольно сложные алгоритмы и математику. Масштабирование битовых образов - это именно одна из таких областей.

Мы рассматривали в четвертой главе, «Механизмы двухмерной графики», способы масштабирования векторных многоугольников. Для этого мы просто умножали каждую из вершин на коэффициент масштабирования и получали новую координату вершины. Затем новые вершины соединялись векторами и получался новый, уже отмасштабированный образ.

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

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

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

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

Давайте подойдем к решению данной проблемы немного с другой стороны. На время забудем об умножении на 0.5 или 2, а подумаем о растягивании и сжатии исходного образа. Другими словами, мы хотим взять исходное количество пикселей и растянуть или сжать их до требуемого количества пикселей. Рисунок 7.11 показывает графическое представление этой идеи.






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

Так как мы знаем, что экран представляет собой матрицу целых, то может показаться, что растягивание какого-либо объекта в нецелое количество раз не будет работать. Однако все не так уж и плохо. Мы можем взять часть исходного изображения и затем поместить его на изображение требуемого размера. Затем используя коэффициент обратный коэффициенту масштабирования, определить индекс следующей части масштабируемого изображения и повторить весь процесс сначала. В результате многократного повторения этой процедуры мы и достигнем желаемого результата.



Вернемся к нашему примеру. Мы хотим в результате масштабирования получить 100 пикселей, которые бы выглядели как исходные 64. Как мы уже посчитали, коэффициент масштабирования должен быть 1.5, следовательно, каждый пиксель должен быть скопирован 1.5 раза. Однако мы не можем этого сделать, так как скопировать только половину пикселя при всем желании не удастся. Испробуем наш альтернативный вариант, и сделаем примерно так:

§

Проиндексируем область значений исходного изображения от 0 до 63;

§          Проиндексируем область значений увеличенного объекта также от 0 до 63, но не целыми, а дробными значениями.

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

Вуаля! Образ увеличился так, как надо. Остался единственный вопрос, какова связь между шагом приращения дробного индекса и коэффициентом масштабирования? Ответ не прост, а очень прост:

1/коэффициент масштабирования

И все! Конечно, мы должны изменять масштаб и по координате X, и по координате Y, но алгоритм можно использовать один и тот же. Более того, если объект имеет одинаковые размеры по координатам Х и Y (то есть если он имеет размеры МхМ, а не MxN), то расчеты могут быть выполнены еще быстрее.



Есть только одна неприятная деталь: мы должны все это делать, используя математику с плавающей запятой, что существенно медленнее, чем расчеты с целыми числами. Но в восемнадцатой главе, «Техника оптимизации», мы изучим, как пользоваться математикой с фиксированной запятой и перепишем этот алгоритм, сделав его более эффективным.

Чтобы показать работу функции масштабирования объектов я написал демонстрационную программу, которая называется SCALE.С. В ней вы можете выбрать одну из четырех текстур и масштабировать ее, используя клавишу левой угловой скобки (или знака «меньше») для уменьшения и клавишу правой угловой скобки (или знака «больше») — для увеличения объекта. Обратите внимание, что масштабирование действительно замедляется при увеличении объекта, но надо помнить, что это только начало, и нам было важнее всего понять, как все это работает. Листинг 7.10 содержит текст программы. Не забудьте при компоновке программы подключить библиотеку GRAPH0.C.

Листинг 7.10. Программа, масштабирующая текстуры стен (SCALE.C).

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ ////////////////////////////////////////

#include <io.h>

#include <conio.h>

#include <stdio.h>

#include <stdlib.h>

#include <dos.h>

#include <bios.h>

#include <fcntl.h>

#include <memory.h>

#include <malloc.h>

#include <math.h>

#include <string.h>

#include "graph0.h" // включаем нашу графическую библиотеку

// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ //////////////////////////////////

sprite object;

pcx_picture text_cells;

// ФУНКЦИИ ///////////////////////////////////////////////

 void Scale_Sprite(sprite_ptr sprite,float scale)

{

// эта функция масштабирует спрайт, рассчитывая количество

// дублирования исходных пикселей, необходимое для получения

// требуемого

размера

char far *work_sprite;

int work offset=0, offset,x,у;

unsigned char data;

float y_scale_index,x_scale_step,y_scale_step,x_scale_index;

// берем первый пиксель исходного изображения



y_scale_index = 0;

// рассчитываем

дробный шаг

y_scale_step = sprite height/scale;

x_scale_step = sprite_width/scale;

// для простоты даем указателю иа спрайт новое имя

work_sprite = sprite->frames [sprite->curr__frame];

// расчет смещения спрайта в видеобуфере

offset = (sprite->y << 8) + (sprite->y << 6) + sprite->x;

// построчно масштабируем спрайт

for (y=0; y<(int) (scale); у++)

{

// копируем следующую строчку в буфер экрана

х_scale_index=0;

for (х=0; x<(int)scale; х++)

{

// проверка на прозрачность пикселя

// (то есть равен ли он 0), если нет - прорисовываем пиксель

if ((data=work_sprite[work_offset+(int)x_scale_index]))

double_buffer[offset+x] = data;

x_scale_index+=(x_scale_step);

} // конец внутреннего цикла (по X)

// используя дробный шаг приращения, определяем следующий

// пиксель исходного изображения

у_scale_index+=y_scale_step;

// переходим к следующей строке видеобуфера

//и растрового буфера спрайта

offset      += SCREEN_WIDTH;

work_offset = sprite_width*(int)(y_scale_index);

} // конец внешнего цикла (по У)

} // end Scale_Sprite /////////////////////////////////////////

void Clear_Double_Buffer(void)

// эта функция очищает дублирующий буфер

// несколько грубо, зато работает

_fmemset(double_buffer, 0, SCREEN_WIDTH * SCREEN_HEIGHT +1);

} // конец Clear_Double_Buffer

// ОСНОВНАЯ ПРОГРАММА /////////////////////////////////////

void main(void)

{

int index, done=0 ;

float scale=64;

// установка видеорежима 320х200х256

Set_Mode(VGA256);

// установка размера спрайта

sprite_width

= sprite_height = 64 ;

// инициализация файла PCX, который содержит

// мультииликационные кадры

PCX__Init ((pcx_picture_ptr)&text_cells) ;

// загрузка файла PCX, который содержит мультипликационные кадры

PCX_Load("textures.pcx", (pcx_picture_ptr)&text_cells,1) ;

// PCX_Show_Buffer((pcx_picture_ptr)&text_cells);

Sprite_Init ( (sprite_ptr) &object, 0, 0, 0, 0 ,0, 0);

// выборка четырех интересующих нас текстур



PCX_Grap_Bitmap((pcx_picture_ptr)&text_cells,

(sprite_ptr)&object,0,0,0);

PCX_Grap_Bitmap((pcx_picture_ptr)&text_cells,

(sprite_ptr)&object,1,1,0);

PCX_Grap_Bitmap((pcx_picture_ptr)&text cells,

(sprite_ptr)&object,2,2,0) ;

PCX_Grap_Bitmap((pcx_picture_ptr)&text_cells,

(sprite_ptr)&object,3,3,0) ;

// резервируем память под дублирующий буфер

Init_Double_Buffer() ;

// позиционируем объект в центре экрана

object.curr frame =0;

object.х          = 160-(sprite_width>>1);

object.у          = 100-(sprite height>>1);

// очищаем дублирующий буфер

Clear_Double_Buffer();

// воспроизводим масштабированную текстуру

Scale_Sprite((sprite_ptr)&object,scale) ;

Show_Double_Buffer(double buffer) ;

_settextposition(24,0);

Printf("Q - Quit, < > - Scale, Space - Toggle.");

// главный цикл while(!done)

{

// нажал ли игрок клавишу?

if (kbhit())

{

switch(getch()) {

case '.': // увеличиваем объект

{

if (scale<180) ( scale+=4;

object.x-=2;

object.y-=2;

} // конец оператора if

} break;

case ',' : // уменьшаем объект

{

if (scale>4) {

scale-=4;

object.x+=2;

object.y+=2;

} // конец оператора if

} break;

case ' ': // смена текстуры

{

// текстуры исчерпались?

if (++object.curr_frame==4)

object. curr_frame=0 ;

} break;

case 'q': // до свидания! {

done=1;

) break;

default:break;

} // конец оператора switch

// очистка дублирующего буфера

Clear_Double_Buffer() ;

// масштабирование спрайта и прорисовка его

//в дублирующем

буфере

Scale_Sprite((sprite_ptr)&object,scale);

// вывод дублирующего буфера на экран

Show_Double_Buffer(double_buffer);

_settextpostion(24,0) ;

printf("Q - Quit,"< > - Scale, Space - Toggle.");

}// конец оператора,if

} // конец оператора..while

// удаление

файла PCX

PCX_Delete((pcx_picture_ptr)&text_cells);

// возврат в текстовый режим

Set_Mode(TEXT_MODE);

} // конец функции main


Содержание раздела