Краткое описание по работе с Direct Draw

         

Программирование DirectDraw


1.       Что Вам необходимо для использования DirectDraw

2.       Инициализация DirectDraw

3.       Создание первичной поверхности и установки для обеспечения смены страниц (flipping)

4.       Загрузка изображений в видеопамять (bitmap)

5.       Загрузка палитры

6.       Настройка прозрачного цвета

7.       Собрать все вместе и готово. Построение динамики.

8.       Очистка по окончании использования


Таким образом мы проинициализировали DirectDraw и изменили видео моду (в отладчике Developer Studio режим 640х480 устанавливать не рекомендую, эффект потрясающий - практически становятся видны только меню и туулбары)

3. Создание первичной и вторичной поверхностей и установки для обеспечения смены страниц (flipping)

В следующем участке кода мы рассмотрим создание первичной и вторичной поверхностей (видеобуферов) . Их объединение часто называют комплексной поверхностью



Пример инициализации поверхностей (первичной и вторичной) DirectDraw

 

 

 

LPDIRECTDRAWSURFACE

lpDDSPrimary; // указатель на первичную поверхность DirectDraw

LPDIRECTDRAWSURFACE

lpDDSBack;    // указатель на вторичную поверхность DirectDraw

BOOL CreatePrimarySurface()

{

    DDSURFACEDESC ddsd;

    DDSCAPS ddscaps;

    HRESULT

ddrval;

    // создадим первичную поверхность с одной вторичной

    memset( &ddsd, 0, sizeof(ddsd) );

    ddsd.dwSize = sizeof( ddsd );

    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;    // поле  ddsCaps

принимается в рассмотрение и в

                                                        // нем имеет значение только количество вторичных

                                                        //буферов(поверхностей)

    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;

    ddsd.dwBackBufferCount = 1;

    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );

    if( ddrval != DD_OK )

    {

        lpDD->Release();

        return FALSE;

    }

    // Теперь получим вторичный буфер (поверхность)

    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;

    ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);

    if( ddrval != DD_OK )

    {

        lpDDSPrimary->Release();

        lpDD->Release();

        return FALSE;

    }

    return TRUE;

}

<


 

4. Загрузка изображений в видеопамять (bitmap)

Следующим шагом является загрузка изображений в видеопамять. Идеальным является случай, когда в видеопамяти достаточно места для того чтобы там ужились все необходимые Вам для разработки изображения. Если ее не хватает , тоже особенно беспокоится не о чем, CreateSurface

создаст поверхности в обычной памяти (правда, это чуть замедлит).

Пример кода загрузки изображений в DirectDraw

 

 

 

/*

 * Функция DDLoadBitmap(IDirectDraw *pdd, LPCSTR

szBitmap) создает поверхность

 * и загружает файл с диска. Параметр szBitmap имя файла изображения.

 */

IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCSTR szBitmap)

{

    HBITMAP hbm;

    BITMAP bm;

    IDirectDrawSurface *pdds;

    // загрузим с диска

    hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, 0, 0,

    LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)        return NULL;

    GetObject(hbm, sizeof(bm), &bm); // получим размер

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

    pdds = CreateOffScreenSurface(pdd, bm.bmWidth, bm.bmHeight);

    if (pdds)

    {

        DDCopyBitmap(pdds, hbm, bm.bmWidth, bm.bmHeight);

    }

    DeleteObject(hbm);

    return

pdds;

}

/*

 * Функция создания поверхности для изображения заданного размера

 * Эта поверхность окажется в видеопамяти, если ее еще достаточно,

 * в противном случае - в системной памяти

 */

IDirectDrawSurface * CreateOffScreenSurface(IDirectDraw *pdd, int dx, int dy)

{

    DDSURFACEDESC ddsd;

    IDirectDrawSurface *pdds;

    //

    // Создание поверхности

    //

    ZeroMemory(&ddsd, sizeof(ddsd));

    ddsd.dwSize = sizeof(ddsd);

    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH; // имеет значение высота и ширина

    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; // это заэкранная поверхность

    ddsd.dwWidth = dx;

    ddsd.dwHeight = dy;

    if (pdd->CreateSurface(&ddsd, &pdds, NULL) != DD_OK)

    {

        return NULL;

    }

    else

    {

        return pdds;

    }

}

/*

 * Функция копирования ранее загруженного изображения в видео поверхность

 * (Используется обычный GDI

интерфейс)

 *  Это, конечно, не самый быстрый способ - но зато надежный

 *  A в данный момент спешить некуда. Если у Вас происходит динамическая подгрузка

 *  изображений в процессе то лучше эту функцию переписать аккуратнее

 */

HRESULT DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int dx, int dy)

{

    HDC hdcImage;

    HDC hdc;

    HRESULT hr;

    HBITMAP

hbmOld;

    //

    // привяжем контекст изображения (DC) к загруженному изображению

    //

    hdcImage = CreateCompatibleDC(NULL);

    hbmOld = (HBITMAP)SelectObject(hdcImage, hbm);

    if ((hr = pdds->GetDC(&hdc)) == DD_OK)

    {

        BitBlt(hdc, 0, 0, dx, dy, hdcImage, 0, 0, SRCCOPY);

        pdds->ReleaseDC(hdc);

    }

    SelectObject(hdcImage, hbmOld);

    DeleteDC(hdcImage);

    return hr;

}

<


Таким образом, мы имеем возможность загрузить все необходимые изображени я в видео поверхности DirectDraw. Для создания динамического изображения остается только организовать их копирование в на поверхности видеоизображения (первичный или вторичный буфер).

5. Загрузка палитры

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

Пример кода создания палитры в DirectDraw

 

 

 

 

 

/*

 * Создание палитры DirectDraw для файла загружаемого с диска

 *  параметр szBitmap имя файла

 */

IDirectDrawPalette * LoadPaletteFromDibFile(IDirectDraw *pdd, LPCSTR

szBitmap)

{

    IDirectDrawPalette* ddpal;

    int i;

    int n;

    int fh;

    PALETTEENTRY ape[256];

    if (szBitmap && (fh = _lopen(szBitmap, OF_READ)) != -1)

    {

        BITMAPFILEHEADER bf;

        BITMAPINFOHEADER bi;

        _lread(fh, &bf, sizeof(bf));

        _lread(fh, &bi, sizeof(bi));

        _lread(fh, ape, sizeof(ape));

        _lclose(fh);

        if (bi.biSize != sizeof(BITMAPINFOHEADER))

            n = 0;

        else if (bi.biBitCount > 8)

            n = 0;

        else if (bi.biClrUsed == 0)

            n = 1 << bi.biBitCount;

        else

            n = bi.biClrUsed;

      //

      // цветовая таблица DIB имеет  BGR кодировку а не RGB

      // сделаем необходимую транспозицию.

      //

        for(i=0; i<N; (pdd- if } ape[i].peBlue = r; ape[i].peRed = ape[i].peBlue; r = ape[i].peRed; BYTE { ) i++CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL) != DD_OK)

    {

        return NULL;

    } else {

        return ddpal;

    }

}

<


В исходный код инициализации DirectDraw необходимо добавить следующие две строчки:

Пример кода загрузки палитры в DirectDraw

 

 

 

    ......................

    lpDDPal        = LoadPaletteFromDibFile(lpDD, szBitmap);   // Создадим и Получим указатель на палитру

    if (lpDDPal)    lpDDSPrimary->SetPalette(lpDDPal);            // и установим эту палитру для первичной видео поверхности

    ....................       

 

5. Настройка прозрачного цвета

В DirectDraw

возможно использование прозрачного цвета(ов) (входа в палитру (color key)) используемого при копировании с поверхности на поверхность. Обычно для этого задается диапазон цветов (входов в палитру), которые не переносятся при копировании (Blt функциях).

Пример кода установки прозрачных цветов в DirectDraw

 

 

 

 

 

    // Установим прозрачный цвет для данного изображения .

    //

    DDCOLORKEY

ddColorKey;

     ddColorKey.dwColorSpaceLowValue = 0xff; // весь диапазон прозрачности - 255 вход в палитру (обычно он черный)

     ddColorKey.dwColorSpaceHighValue = 0xff;

    //  Далее необходимо сделать что-то типа для вех поверхностей (изображений ) для которых нужен эффект

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

  [Имя указателя на DirectDraw

поверхность]->SetColorKey( DDCKEY_SRCBLT, &ddck );

 

7. Собрать все вместе и готово. Построение динамики.

Теперь осталось только организовать цикл по обновлению вторичной поверхности и после его обновления проведения смены экранов (Flip) . Этот цикл лучше всего организовывать на цикле обработки сообщений Вашего приложения (OnIdle), таким образом Вы получите максимальную производительность для конкретной машины на которой выполняется ваше приложение. Сажать это на таймер я не советую - Вам просто будет сложно разобраться и все так подогнать, чтобы с одной стороны по максимуму использовать возможности машины, а с другой не перегружать очередь сообщений. Для общей синхронизации разумно использовать функции мультимедиа типа функции timeGetTime() которая выдает время в мс. Также учтите что чем больше нитей (THREAD) Вы организуете для обеспечения расчетов расположения изображений тем сложнее Вам будет добиться плавности (Windows 95 совсем не многозадачка с вытеснением и квантом времени я не нашел как управлять). Единственное ,что можно использовать для увеличения плавности - Sleep(0) для текущей нити приложения. Но это о другом - просто лирическое отступление - плач по отсутствию возможностей.



Обновления экрана

 

 

 

 

 

void

UpdateScreen( void )

{

    HRESULT ddrval;

    RECT mRect;

    int xpos, ypos;

   // Выберем весь экран и скопируем на вторичную поверхность изображения фона

   // если Вы, конечно, его используете

    SetRect(&mRect, 0, 0, 640, 480);

    // lpDDBackgraund - здесь поверхность фонового изображения

    // кстати оно может быт больше экрана - тогда возможно копирование только его части - типа перемещения по карте

    ddrval = lpDDSBack->BltFast( 0, 0, lpDDBackgraund, &mRect, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);

    if( ddrval != DD_OK )

    {

        // не смогли - вернемся       

        return;

    }

   while ([цикл по всем изображениям, которые должны быть перенесены на экран])     

   {   

        // Получить текущее изображение (указатель lpDDCurrentImage) из списка, массива - чего было задумано,

        // а также его размера mWidth, mHigh, и местоположения x и y

        .......................

        // после получения указанных параметров

        SetRect(&mRect, 0, 0,mWidth , mHigh);  

        ddrval = lpDDSBack->BltFast( x, y, lpDDCurrentImage,    &mRect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT );

           if( ddrval != DD_OK )

         {

        //не смогли - вернемся       

              return;

         } 

    }          

    // Теперь все нарисовано - можно поменять экран

    ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT );

}

К данному коду надо сделать ряд замечаний :

·         BltFast - функция, конечно, хорошая и по названию зазывающая, но не всегда идет - тогда используй обычный Blt (он видимо помедленнее - но надежней)



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

 

8. Очистка по окончании использования

По окончании работы не забудьте вызвать Release() для всех порожденных Вами объектов DirectDraw. Что то типа приведенного ниже кода.

Очистка после себя

 

 

 

void finiObjects( void )

{

    if( lpDD != NULL )

    {

       

        if( lpDDSPrimary != NULL )

        {

            lpDDSPrimary->Release();

            lpDDSPrimary = NULL;

        }

        if( lpDDSOne != NULL )

        {

            lpDDSOne->Release();

            lpDDSOne = NULL;

        }

        if( lpDDPal != NULL )

        {

            lpDDPal->Release();

            lpDDPal = NULL;

    }

    lpDD->Release();

    lpDD = NULL;

}


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