int imagebitdepth=bmpinfohdr.biBitCount;
int imagesize=bmpinfohdr.biSizeImage;
if (imagesize==0) imagesize=((imagew*(imagebitdepth/8)+3) & ~3)*imageh;
if (bmpinfohdr.biCompression!=BI_RGB) {
TRACE("compressed BMP formatn");
return 0;
}
TRACE("loading '%s': width=%d height=%d depth=%dn", filename, imagew, imageh, imagebitdepth);
if (imagebitdepth==8) {
int ncolors;
if (bmpinfohdr.biClrUsed==0) ncolors=256;
else ncolors=bmpinfohdr.biClrUsed;
RGBQUAD* quad=new RGBQUAD[ncolors];
bmp.read((char*)quad, sizeof(RGBQUAD)*ncolors);
if (installpalette) CreatePalette(quad, ncolors);
delete[] quad;
}
BYTE* buf=new BYTE[imagesize];
bmp.read(buf, imagesize);
if (!Copy_Bmp_Surface(surf, &bmpinfohdr, buf)) {
TRACE("copy failedn");
delete[] buf;
surf->Release();
return 0;
}
delete[] buf;
return surf;
}
Сначала эта функция определяет размеры изображения из BMP-файла с помощью функции GetBmpDimensions() — простой функции класса DirectDrawWin, которая открывает BMP-файл и извлекает из заголовка ширину и высоту изображения. На основании полученных данных создается новая поверхность с использованием версии CreateSurface(), которая создает поверхность по ее размерам. Новая поверхность заполнена случайными пикселями, но мы не стираем ее, потому что вскоре значение каждого пикселя будет задано в соответствии с содержимым BMP-файла.
Затем мы открываем BMP-файл с помощью класса ifstream и извлекаем из него данные заголовка. Далее проверяется сигнатура файла; если проверка дает отрицательный результат, BMP-файл может содержать неверную информацию, поэтому функция завершает работу.
Дополнительные данные заголовка извлекаются с помощью структуры BITMAPINFOHEADER. Обратите внимание: после заполнения структуры текущая позиция в файле ifstream изменяется в соответствии со значением поля biSize. Это сделано для того, чтобы в будущем, при увеличении размера структуры BITMAPINFOHEADER, наша программа нормально работала с новыми BMP-файлами.
Ширина и высота изображения уже известны, поэтому читать значения полей biWidth и biHeight структуры BITMAPINFOHEADER не нужно. Функция CreateSurface() считывает глубину пикселей (biBitCount) и размер изображения (biSizeImage). Как упоминалось выше, поле biSizeImage часто равно нулю, поэтому мы проверяем его значение. Снова приведу соответствующий фрагмент кода:
int imagesize=bmpinfohdr.biSizeImage;
if (imagesize==0) imagesize=((imagew*(imagebitdepth/8)+3) & ~3)*imageh;
Если поле biSizeImage отлично от нуля, мы оставляем текущее значение. В противном случае его приходится вычислять самостоятельно по известному размеру и глубине пикселей изображения. Обратите внимание на то, что выравнивание по границе параграфа выполняется за счет битовых операций.
И последняя проверка: по содержимому поля biCompression мы убеждаемся, что BMP-файл не содержит сжатых данных, не поддерживаемых нами. Для сжатых файлов функция возвращает ноль, код неудачного завершения.
Если изображение является палитровым, мы загружаем палитру. Количество элементов палитры в файле определяется полем biClrUsed, но это поле также может быть равно нулю. В этом случае предполагается, что присутствуют все 256 элементов палитры. Палитра загружается лишь в том случае, если параметр installpalette равен TRUE; тогда вызывается функция CreatePalette(). Вскоре мы рассмотрим код этой функции.
Следующий этап — чтение графических данных, которое в нашем случае выполняется одним вызовом функции ifstream::read(). Графические данные передаются функции Copy_Bmp_Surface(), отвечающей за пересылку данных новой поверхности. После возврата из функции Copy_Bmp_Surface() буфер с графическими данными освобождается. BMP-файл автоматически закрывается при возвращении из функции CreateSurface() (поскольку локальный объект ifstream выходит из области видимости).
Функция CreatePalette()
Если второй аргумент функции CreateSurface() равен TRUE, CreatePalette() создает и заполняет объект DirectDrawPalette данными, полученными из BMP-файла. Функция CreatePalette() выглядит так:
BOOL DirectDrawWin::CreatePalette(RGBQUAD* quad, int ncolors){
if (palette) palette->Release(), palette=0;
PALETTEENTRY pe[256];
ZeroMemory(pe, sizeof(pe));
for(int i=0; i<ncolors; i++) {
pe[i].peRed = quad[i].rgbRed;
pe[i].peGreen = quad[i].rgbGreen;
pe[i].peBlue = quad[i].rgbBlue;
}
HRESULT r=ddraw2->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, pe, &palette, 0);
if (r!=DD_OK) {
TRACE("failed to reate DirectDraw paletten");
return FALSE;
}
primsurf->SetPalette(palette);
return TRUE;
}
Палитры DirectDraw создаются функцией CreatePalette() интерфейса DirectDraw, которой передается массив структур PALETTEENTRY. Чтобы выполнить это требование, приходится преобразовывать массив структур RGBQUAD, извлеченный из BMP-файла, во временный массив (структуры PALETTEENTRY и RGBQUAD очень похожи, поэтому такое преобразование оказывается тривиальным). Затем созданный массив передается функции CreatePalette(). Флаг DDPCAPS_ALLOW256 сообщает, что мы намерены задать значения всех 256 элементов палитры. Если вы пропустили главу 4 (конечно же, нет!), вернитесь к ней и ознакомьтесь с возможными аспектами использования этого флага.
Наконец, функция SetPalette() интерфейса DirectDrawSurface() присоединяет палитру к поверхности. Обратите внимание на то, что палитра присоединяется к первичной поверхности. Хотя палитры можно присоединять и к другим поверхностям, на системную палитру влияет только палитра, присоединенная к первичной поверхности.
Передача графических данных
Как видно из функции CreateSurface(), передача графических данных BMP-файла на поверхность осуществляется функцией Copy_Bmp_Surface(). Функция Copy_Bmp_Surface() пользуется услугами четырех вспомогательных функций, каждая из которых специализируется на пикселях определенной глубины. Код Copy_Bmp_Surface() выглядит так:
BOOL DirectDrawWin::Copy_Bmp_Surface(LPDIRECTDRAWSURFACE surf, BITMAPINFOHEADER* bmphdr, BYTE* buf) {
if (surf==0 || bmphdr==0 || buf==0) return FALSE;
int imagew=bmphdr->biWidth;
int imageh=bmphdr->biHeight;
int imagebitdepth=bmphdr->biBitCount;
BOOL ret=FALSE;
if (imagebitdepth==8) {
if (displaydepth==8) ret=Copy_Bmp08_Surface08(surf, buf, imagew, imageh);
} else if (imagebitdepth==24) {
if (displaydepth==16) ret=Copy_Bmp24_Surface16(surf, buf, imagew, imageh);
else if (displaydepth==24) ret=Copy_Bmp24_Surface24(surf, buf, imagew, imageh);
else if (displaydepth==32) ret=Copy_Bmp24_Surface32(surf, buf, imagew, imageh);
}
return ret;
}
Вспомогательные функции предназначены для передачи графических данных в зависимости от глубины пикселей BMP-файла и текущего видеорежима. Все четыре функции получают одни и те же четыре аргумента: указатель на поверхность-приемник, буфер с графическими данными из BMP-файла, ширину и высоту изображения. Каждая функция копирует графические данные BMP-файла на поверхность-приемник.
8-битные поверхности
Начнем с самой простой из четырех функций, Copy_Bmp08_Surface08(). Она выглядит так:
BOOL DirectDrawWin::Copy_Bmp08_Surface08(LPDIRECTDRAWSURFACE surf, BYTE* bmpbuf, int w, int h) {
if (surf==0 || bmpbuf==0) return FALSE;
DDSURFACEDESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.dwSize = sizeof(desc);
HRESULT r=surf->Lock(0, &desc, DDLOCK_WAIT | DDLOCK_WRITEONLY, 0);
if (r!=DD_OK) {
TRACE("ShowBmp: Lock() failedn");
return FALSE;
}
int bytesgiven=(w+3) & ~3;
BYTE* surfbits = (BYTE*)desc.lpSurface;
BYTE* imagebits = (BYTE*)(&bmpbuf[(h-1)*bytesgiven]);
for(int i=0; i<h; i++) {
memcpy(surfbits, imagebits, w);
surfbits += desc.lPitch;
imagebits -= bytesgiven;
}
surf->Unlock(0);
return TRUE;
}
После проверки обоих аргументов-указателей мы подготавливаем экземпляр структуры DDSURFACEDESC(desc) и используем его при вызове функции Lock() интерфейса DirectDrawSurface. После возвращения из функции Lock() поле lpSurface содержит указатель на память поверхности, и мы можем спокойно изменять содержимое поверхности через этот указатель до вызова Unlock(). Безопасная работа с поверхностью стала возможной только потому, что мы указали флаг DDLOCK_WRITEONLY. Если вы собираетесь осуществлять и чтение, и запись, не устанавливайте этот флаг.
Далее мы инициализируем целую переменную bytesgiven. Присваиваемое значение определяется шириной изображения (w), выровненного по границе параграфа. Получившаяся величина равна объему памяти, необходимой для хранения одной строки пикселей. Если ширина изображения кратна четырем, переменная bytesgiven совпадает с w.