Функция CreateSurface()
Функция CreateSurface() требует, чтобы изображение в передаваемом BMP-файле было палитровым или беспалитровым в зависимости от текущего видеорежима. Она не станет загружать палитровые изображения на беспалитровую поверхность, и наоборот. В принципе это возможно, но непрактично. Загрузить палитровое изображение на беспалитровую поверхность довольно просто, но глупо, потому что при этом будет использоваться лишь малая часть возможностей поверхности (всего 256 цветов из 16 миллионов). С другой стороны, загрузка беспалитровых изображений на палитровую поверхность потребует программного сокращения миллионов цветов до 256-цветной палитры.
Давайте посмотрим, как реализована функция CreateSurface() (см. листинг 5.1).
Листинг 5.1. Функция CreateSurface()
LPDIRECTDRAWSURFACE DirectDrawWin::CreateSurface(LPCTSTR filename, BOOL installpalette) { int imagew, imageh; GetBmpDimensions( filename, imagew, imageh ); LPDIRECTDRAWSURFACE surf=CreateSurface( imagew, imageh ); if (surf==0) { TRACE("CreateSurface(filename) failed to create surface\n"); return 0; }
ifstream bmp( filename, ios::binary | ios::nocreate ); if (!bmp.is_open()) { TRACE("LoadSurface: cannot open Bmp file\n"); return 0; } BITMAPFILEHEADER bmpfilehdr; bmp.read( (char*)&bmpfilehdr, sizeof(bmpfilehdr) ); char* ptr=(char*)&bmpfilehdr.bfType; if (*ptr!='B' || *++ptr!='M') { TRACE("invalid bitmap\n"); return 0; } BITMAPINFOHEADER bmpinfohdr; bmp.read( (char*)&bmpinfohdr, sizeof(bmpinfohdr) ); bmp.seekg( sizeof(bmpfilehdr)+bmpinfohdr.biSize, ios::beg ); 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 format\n"); return 0; } TRACE("loading '%s': width=%d height=%d depth=%d\n", 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 failed\n"); delete [] buf; surf->Release(); return 0; } delete [] buf; return surf; } |
br> Сначала эта функция определяет размеры изображения из 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 выходит из области видимости).