DirectDraw es un manejador de memoria de vídeo con el cual puedes manipular directamente, desde esta, imágenes y mostrarlas de forma eficiente por pantalla.
ddraw.lib e incluir en nuestro código la cabecera ddraw.h. Como vamos a trabajar con DirectDraw7, la última versión que se desarrolló, necesitaremos agregar también dxguid.lib.ShowWindow y\o UpdateWindow.
Para configurar e iniciar la programación con DirectDraw tendremos que seguir los siguientes pasos, cada uno de ellos se basan en llamadas a funciones que devuelven una flag que si es distinta de DD_OK significa que ese paso ha fallado. DD_OK es del tipo HRESULT.
DirectDrawCreateEx pasándole 4 parámetros: (void**).IID_IDirectDraw7, que es un identificador de clase. SetCooperativeLevel al cual le pasas el HWND de la ventana y los flags de configuración, estos son:EnumDisplayModes para ver los soportados.#include <ddraw.h> ... #define MSGBOX(body) MessageBox(NULL,body,"",MB_OK); ... LPDIRECTDRAW7 lpdd; ... if (DirectDrawCreateEx(NULL, (void**)&lpdd, IID_IDirectDraw7, NULL) != DD_OK) MSGBOX("Falla la creación de DDraw!"); if (lpdd->SetCooperativeLevel (hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN) != DD_OK) MSGBOX("Falla el cambio de modo a cooperativo!"); if (lpdd->SetDisplayMode (640, 480, 32, 0, 0) != DD_OK) MSGBOX("Falla la asignación del modo de pantalla!");
Existen dos tipos de superficies que una aplicación que implemente doble buffer con DDraw debe tener:
Recuerda, una superficie es del tipo LPDIRECTDRAWSURFACE7.
LPDIRECTDRAWSURFACE7 primarySurface; LPDIRECTDRAWSURFACE7 backSurface;
Una vez tengamos definidas los objetos correspondientes a las superficies de DDraw necesitaremos configurarlos:
CreateSurface.GetAttachedSurface.DDSURFACEDESC2 ddsd; ZeroMemory(&ddsd, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1; if (lpdd->CreateSurface (&ddsd, &primarySurface, NULL) != DD_OK) MSGBOX ("Falla la creación de la superficie principal"); DDSCAPS2 ddscaps; ddscaps.dwCaps = DDSCAPS_BACKBUFFER; if (primarySurface->GetAttachedSurface(&ddscaps, &backSurface) != DD_OK) MSGBOX ("Falla el enlace con el backbuffer");
* ZeroMemory limpia la memoria para DDSURFACEDESC2, de esa forma no tendremos problemas más tarde.
Una vez acabemos una aplicación de DDraw debemos dejarlo todo tal cual estaba, para ello seguiremos los siguientes pasos:
RestoreDisplayMode del objeto DirectDraw.SetCooperativeLevel pero esta vez pasándole la flag DDSCL_NORMAL.Release.Release).Release.lpdd->RestoreDisplayMode(); lpdd->SetCooperativeLevel(hwnd, DDSCL_NORMAL); primarySurface->Release(); lpdd->Release();
Cuando se pierden las superficies… Hay que recuperarlas.
Esto ocurre, por ejemplo, cuando el usuario cambia de aplicación mediante un alt+tab o apretando a la tecla de windows. La aplicación pasa a segundo plano y al retornar GDI vuelve a quedarse con el dominio de la ventana, se han borrado las superficies! Cuando esto ocurre los métodos Flip o BltFast ya no retornan DD_OK sino DDERR_SURFACELOST. Aún así podemos recuperarlas fácilmente: únicamente se ha de llamar al método Restore de cada superficie (o si lo preferimos al RestoreAllSurfaces del objeto DDraw), pero al recuperarlas ya no tienen cargadas las imágenes, tendremos que volver a cargarlas de nuevo.
WM_ACTIVATEAPP.Para DDraw una imágen también es una superficie, por lo que los pasos para mostrar una es cargarla y luego volcarla sobre un objeto LPDIRECTDRAWSURFACE. Algo fácil si seguimos los siguientes pasos:
BITMAP bmp; HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, pathBMP, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION); GetObject(hBitmap, sizeof(bmp), &bmp);
dwWidth y dwHeight.LPDIRECTDRAWSURFACE7 bmpSurface; DDSURFACEDESC2 ddsd; ZeroMemory(&ddsd, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; ddsd.dwWidth = bmp.bmWidth; ddsd.dwHeight = bmp.bmHeight; lpdd->CreateSurface(&ddsd, &bmpSurface, NULL);
HDC hdcImagen = CreateCompatibleDC(NULL); HDC hdc; HBITMAP hBitmapAnt = (HBITMAP)SelectObject(hdcImagen, hBitmap); bmpSurface->GetDC(&hdc); BitBlt(hdc, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcImagen, 0, 0, SRCCOPY); bmpSurface->ReleaseDC(hdc); SelectObject(hdcImagen, hBitmapAnt); DeleteDC(hdcImagen); DeleteObject(hBitmap);
RECT rectOrig; rectOrig.left = 0; rectOrig.top = 0; rectOrig.right = 640; rectOrig.bottom = 480;
backSurface->BltFast(0, 0, bmpSurface, &rectOrig, DDBLTFAST_WAIT); primarySurface->Flip(backSurface, DDFLIP_WAIT);
Una vez tenemos las superficies bien montaditas y configuraditas podemos utilizar las funciones de GDI para escribir sobre ellas, es decir, podemos escribir textos, dibujar y todas esas cosas tan monas que hacemos sobre GDI pero esta vez sobre las superficies de DDraw.
Flip (sí, sí… como el saltamontes de la abeja Maya):primarySurface->Flip(backSurface, DDFLIP_WAIT);
getHDC al cual le pasas un puntero de HDC y en él te vuelca el HDC deseado.HDC hdc; if (Flip(backSurface->GetDC(&hdc) == DD_OK) { SetBkColor(hdc, RGB(0, 0, 255)); SetTextColor(hdc, RGB(0, 0, 0)); TextOut(hdc, 0, 0, "hola", lstrlen("hola")); Flip(backSurface->ReleaseDC(hdc); }
El color keying es el concepto de indicar a DDraw que en toda una imágen un determinado color funcionará como transparente. De esa forma, cuando nosotros mostremos dicha imágen, las partes con ese color no se veran.
Para poder realizar el color keying es necesario un objeto del tipo DDCOLORKEY y dar a este su valor bajo (dwcolorSpaceLowvalue) y valor alto (dwcolorSpaceHighValue), que serán utilizados como segun lo asignemos a la superficie. Para ello, asignarlo, llamamos al método: SetColorKey pasandole los flags deseados y un puntero al DDCOLORKEY.
SetColorKey:Por ejemplo, para asignar al verde una transparencia:
DDCOLORKEY ddck; ddck.dwColorSpaceHighValue = 0x00FF00; ddck.dwColorSpaceLowValue = 0x00FF00; bmpSurface2->SetColorKey(DDCKEY_SRCBLT, &ddck);
No debemos olvidar ahora que al volcar la imágen sobre la superficie se ha de indicar que se tiene que tener en cuenta el color keying:
backSurface->BltFast(340, i, bmpSurface2, &rectOrig2, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
Hacer esto no es tarea compleja, únicamente debemos añadir dos valores más a las propiedades del DDSURFACEDESC2, la ddckCKSrcBlt.dwColorSpaceLowValue y la ddckCKSrcBlt.dwColorSpaceHighValue correspondientes a los de un DDCOLORKEY. También tendremos que indicar mediante una flag más (DDSD_CKSRCBLT) que se tenga en cuenta este color key.
DDSURFACEDESC2 ddsd; ZeroMemory(&ddsd, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_CKSRCBLT; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; ddsd.dwWidth = bmp.bmWidth; ddsd.dwHeight = bmp.bmHeight; ddsd.ddckCKSrcBlt.dwColorSpaceLowValue = 0x00ff00; ddsd.ddckCKSrcBlt.dwColorSpaceHighValue = 0x00ff00;
El blitter es la parte de la gráfica que puede manipular los datos de las imágenes, para acceder a él mediante DirectDraw se utilizan los método Blt y BltFast de una superficie. Con el primero, Blt, puedes realizar más operaciones que con BltFast pero también es más lento, veamos qué parámetros necesita:
Blt) puedes poner un NULL.dwFillColor del DDBLTFX.dwDDFX de la DDBLFFX para indicar los efectos que se tendrán en cuenta.dwDDROPS para operaciones de raster.dwRotationAngle para indicar el ángulo de rotación.Blt, sus propiedades más curiosas (dwSize: Tamaño (dado mediante el sizeof) de la estructura.dwDDFX: Flags para aplicar efectos de blittingdwRotationAngle, dwROPdwFillColor para rellenar de un color sólido.ddckDestColorKey y ddckSrcColorKey para asignar color keys. Estos son del tipo DDCOLORKEY.DDBLTFX l; ZeroMemory(&l, sizeof(l)); l.dwSize = sizeof(l); l.dwFillColor = 0x0000ff; backSurface->Blt(NULL, NULL, NULL, DDBLT_COLORFILL, &l);
backSurface->Blt(NULL, bmpSurface, NULL, DDBLT_WAIT, NULL);
RECT rectOrig2, rectOrig3; rectOrig2.left = 50; rectOrig2.top = 50; rectOrig2.right = 100; rectOrig2.bottom = 100; rectOrig3.left = 0; rectOrig3.top = 0; rectOrig3.right = 100; rectOrig3.bottom = 100; backSurface->Blt(&rectOrig2, bmpSurface, &rectOrig3, DDBLT_WAIT, NULL);
DDBLTFX l; ZeroMemory (&l, sizeof(l)); l.dwSize = sizeof(l); RECT rectOrig2; rectOrig2.left = 0; rectOrig2.top = 0; rectOrig2.right = 100; rectOrig2.bottom = 100; backSurface->Blt(&rectOrig2, bmpSurface2, NULL, DDBLT_WAIT, &l);
// Basándonos en el código anterior l.dwDDFX = DDBLTFX_MIRRORLEFTRIGHT; backSurface->Blt(&rectOrig2, bmpSurface2, NULL, DDBLT_WAIT | DDBLT_DDFX, &l);
// Basándonos en el código anterior DDCOLORKEY ddck; ddck.dwColorSpaceHighValue = 0x00FF00; ddck.dwColorSpaceLowValue = 0x00FF00; l.ddckSrcColorkey = ddck; backSurface->Blt(&rectOrig2, bmpSurface2, NULL, DDBLT_WAIT | DDBLT_KEYSRC, &l);
El BltFast sirve también para copiar de una superficie a otra pero lo hace de forma más rápida que el Blt y sin aplicar efectos. Sus parámetros son los siguientes:
El truco para crear una animación de sprites está en el rectángulo que indica la zona de la superficie que se dibujará. Cuando tenemos una superficie con un imágen cargada tenemos que indicar al método BltFast de la superficie donde queramos cargarla un rectángulo y la posición donde se dibujará. La gracia es que el rectángulo vaya moviendose por una gran imágen donde esten los frames de la animación del sprite pero dibujándose siempre en el mismo sitio.
Por ejemplo, imaginemos una imágen que contiene 4 dibujos de una célula, estos corresponden a una rotación donde llega hacer una vuelta completa en estos 4 dibujos\frames. La imágen es de 400×100 (donde cada célula ocupa 100×100). Modificaremos el rectángulo de la siguiente forma:
RECT rectOrig; int y = 0; ... rectOrig.left = y; rectOrig.top = 0; rectOrig.right = y + 100; rectOrig.bottom = 100;
Y cada vez que movamos la célula haremos:
y = (y == 300) ? 0 : y + 100;
Pero siempre la dibujaremos en el mismo sitio:
backSurface->BltFast(0, 0, bmpSurface, &rectOrig, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
Esto es hacer que en una superficie sólo se muestre una porción, sería algo así como aplicar una máscara a esta. Para poder realizar esto necesitamos un objeto LPDIRECTDRAWCLIPPER que se crea mediante el método CreateClipper de un objeto LPDIRECTDRAW7, a este hay que pasarle 3 argumentos, pero únicamente el 2º es importante, que es un puntero a un objeto LPDIRECTDRAWCLIPPER (el primero se pasará como 0 y el tercero como NULL).
LPDIRECTDRAWCLIPPER clipper; lpdd->CreateClipper(0, &clipper, NULL);
Una vez tengas el objeto necesitarás asignarle una lista de regiones visibles, esto se hace mediante la estructura RGNDATA, esta contiene dos elementos: Buffer (donde se guardarán los RECTs utilizados para indicar las regiones) y una estructura RGNDATAHEADER que indica cómo se debe tratar ese Buffer. Reservaremos el tamaño del buffer con malloc (y no nos olvidaremos de liberar esa porción de memoria, verdad?) ya que es un array y una vez que asignemos las regiones deberemos hacerlo de arriba abajo.
La RGNDATAHEADER contiene las siguientes propiedades útiles:
dwSize, el tamaño.iType, el tipo de regiones, utilizaremos: RDH_RECTANGLES.nCount, int, número de regiones.nRgnSize, tamaño de las regiones, como utilizaremos RECTs le pasaremos algo parecido a esto: sizeof(RECT) * nCount.rcBound, la superficie donde se aplicará el clipping.LPRGNDATA lpClipList = (LPRGNDATA)malloc(sizeof(RGNDATAHEADER) + sizeof(RECT)); RECT rcClipRect = {300,300,640,480}; memcpy(lpClipList->Buffer, &rcClipRect, sizeof(RECT)); lpClipList->rdh.dwSize = sizeof(RGNDATAHEADER); lpClipList->rdh.iType = RDH_RECTANGLES; lpClipList->rdh.nCount = 1; lpClipList->rdh.nRgnSize = sizeof(RECT); lpClipList->rdh.rcBound = rcClipRect;
Una vez tengamos la ClipList tendremos que asignarla al clipper mediante el método de este SetClipList. Y para finalizar asignar el clipper a la superficie deseada con el método de la superficie SetClipper.
clipper->SetClipList(lpClipList, 0)); backSurface->SetClipper(clipper)); free(lpClipList);
No funciona del todo bien
Blt o BltFast, para ello existen funciones de más bajo nivel y con las que tendrás acceso absoluto a los datos de una superficie. Estos son: Lock y Unlock (para bloquearla) y lPitch y lpSurface (del DDSURFACEDESC2) para acceder a ellas.if (FAILED(clipper->SetClipList(lpClipList, 0))) ...