netlib.narod.ru | < Назад | Оглавление | Далее > |
В наших программах чтением BMP-файлов занимается класс DirectDrawWin. Впервые эта возможность была использована в главе 3, где в программе Bounce BMP-файл загружался на поверхность. То же самое происходит и в программе BmpView, но сначала давайте рассмотрим соответствующий программный код.
Поддержка работы с BMP-файлами в классе DirectDrawWin обеспечивается функцией CreateSurface(). Существуют две версии CreateSurface(): первая в качестве аргументов получает параметры поверхности, а вторая — имя BMP-файла. Вторая версия CreateSurface() загружает BMP-файл, затем создает новую поверхность, параметры которой совпадают с параметрами изображения, и копирует содержимое файла на поверхность.
Функция 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() 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; } |
Сначала эта функция определяет размеры изображения из 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 выходит из области видимости).
Если второй аргумент функции 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 create DirectDraw palette\n"); 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-файла на поверхность-приемник.
Начнем с самой простой из четырех функций, 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() failed\n"); 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.
Указатель на поверхность (surfbits) инициализируется значением поля lpSurface. Этот указатель используется для обращений к памяти поверхности. Указатель на графические данные (imagebits) инициализируется адресом последней строки пикселей BMP-файла, поскольку в формате BMP изображение хранится в перевернутом виде.
Затем мы в цикле перебираем все строки пикселей изображения. Благодаря тому, что формат графических данных BMP-файла совпадает с форматом поверхности, для копирования можно воспользоваться функцией memcopy(). Для поверхностей остальных типов такая удобная возможность часто отсутствует. Поле lPitch определяет смещение для указателя на поверхность при переходе к следующей строке. Вспомните, что в этом поле хранится шаг поверхности, который может не совпадать с ее шириной. Целая переменная bytesgiven аналогичным образом используется для перехода к следующей строке буфера графических данных. Поскольку чтение начинается с конца буфера, указатель imagebits уменьшается с каждой очередной итерацией.
Наконец, мы вызываем функцию Unlock() интерфейса DirectDrawSurface и в качестве аргумента передаем ей ноль. С помощью этого аргумента можно сбалансировать вызовы Lock() и Unlock() при многократной блокировке одной поверхности. Для сценариев с однократной блокировкой (включая наш) можно просто передать ноль.
Загрузка 8-битных изображений выполняется достаточно просто. Давайте перейдем к 16-битным поверхностям, с ними дело обстоит значительно сложнее. Помимо учета разных типов 16-битных форматов пикселей нам придется сокращать количество цветов. 24-битные данные передаются на 16-битную поверхность, поэтому во время передачи необходимо «урезать» каждую цветовую составляющую. Функция Copy_Bmp24_Surface16() приведена в листинге 5.2.
Листинг 5.2. Функция Copy_Bmp24_Surface16() |
BOOL DirectDrawWin::Copy_Bmp24_Surface16(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("Copy_Bmp24_Surface16: Lock() failed\n"); return FALSE; } int bytesrequired = w * 3; int bytesgiven = (bytesrequired + 3) & ~3; BYTE* surfbits = (BYTE*)desc.lpSurface; BYTE* imagebits = (BYTE*)(&bmpbuf[(h - 1) * bytesgiven]); float REDdiv = (float)256/(float)pow(2, numREDbits); float GREENdiv = (float)256/(float)pow(2, numGREENbits); float BLUEdiv = (float)256/(float)pow(2, numBLUEbits); for(int i = 0; i < h; i++) { USHORT* pixptr = (unsigned short*)surfbits; RGBTRIPLE* triple = (RGBTRIPLE*)imagebits; for (int p = 0;p < w; p++) { float rf = (float)triple->rgbtRed/REDdiv; float gf = (float)triple->rgbtGreen/GREENdiv; float bf = (float)triple->rgbtBlue/BLUEdiv; WORD r = (WORD)((WORD)rf << loREDbit); WORD g = (WORD)((WORD)gf << loGREENbit); WORD b = (WORD)((WORD)bf << loBLUEbit); *pixptr = (WORD)(r | g | b); triple++; pixptr++; } surfbits += desc.lPitch; imagebits -= bytesgiven; } surf->Unlock(0); return TRUE; } |
Хотя по своей структуре функция Copy_Bmp24_Surface16() напоминает Copy_Bmp 08_Surface08(), она устроена сложнее по причинам, уже упоминавшимся, а также потому, что значение каждого пикселя приходится задавать отдельно. Давайте посмотрим, что происходит в этой функции.
Сначала функция Lock() интерфейса DirectDrawSurface используется для получения указателя на поверхность. Затем мы инициализируем две целые переменные, bytesrequired и bytesgiven. Значение bytesrequired равно количеству байт, необходимых для представления строки пикселей. Поскольку мы работаем с 24-битными пикселями, для получения этой величины достаточно умножить ширину изображения на три (по три байта на пиксель). По значению bytesrequired рассчитывается значение bytesgiven, которое равно количеству байт для хранения строки пикселей в памяти (с учетом выравнивания по границе параграфа). Значение bytesgiven используется для перебора строк пикселей в графических данных BMP-файла.
Затем мы инициализируем указатели surfbits и imagebits; первый указывает на память поверхности, а второй — на буфер графических данных. Как и в функции Copy_Bmp08_Surface08(), imagebits указывает на последнюю строку буфера.
Три следующие строки связаны с сокращением цветов. Мы вычисляем три величины (по одной для каждой цветовой составляющей), которые будут использоваться для обработки составляющих, полученных из буфера графических данных. Они зависят от количества бит в представлении каждой цветовой компоненты на поверхности (чаще всего 5 или 6 бит). Обратите внимание на то, что эти величины вычисляются за пределами цикла. Операция деления и вызов функции pow() внутри цикла могли бы существенно замедлить работу программы.
Назначение пикселей происходит во вложенном цикле. Внешний цикл перебирает строки пикселей, а внутренний задает значение для каждого пикселя строки. Внутренний цикл инициализирует два указателя, pixptr и triple, которые используются для обращения к текущему пикселю. Переменная pixptr указывает на память поверхности, а triple — на буфер графических данных. Обратите внимание — pixptr объявлен как указатель на 16-битный тип USHORT. В этом случае для перехода к следующему пикселю достаточно увеличить значение указателя. Аналогично triple указывает на 24-битный тип RGBTRIPLE.
Внутренний цикл извлекает три цветовые составляющие каждого пикселя и делит их на ранее вычисленную величину. Значения с плавающей точкой, использованные при вычислениях, преобразуются к целым и сдвигаются к нужной позиции в соответствии с переменными loREDbit, loGREENbit и loBLUEbit. Окончательный результат представляет собой тройку «урезанных» цветовых составляющих. Побитовый оператор OR упаковывает составляющие в единую величину, и результат заносится в память поверхности. Указатели pixptr и triple инкрементируются для перехода к следующему пикселю.
Мы рассмотрели доступ к 16-битным поверхностям, и все самое сложное осталось позади. Для 24- и 32-битных поверхностей сокращение цветов уже не требуется, поэтому вычислить значение пикселя оказывается проще. В основном нам нужно лишь извлечь цветовые составляющие и сдвинуть их в позицию, определяемую расположением и форматом пикселя. Для 24-битных поверхностей процесс можно оптимизировать, если формат пикселей поверхности совпадает с форматом пикселей BMP-файла. 24-битные поверхности обрабатываются функцией Copy_Bmp24_Surface24() (см. листинг 5.3).
Листинг 5.3. Функция Copy_Bmp24_Surface24() |
BOOL DirectDrawWin::Copy_Bmp24_Surface24(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("Copy_Bmp24_Surface24: Lock() failed\n"); return FALSE; } int bytesrequired = w * 3; int bytesgiven = (bytesrequired + 3) & ~3; BYTE* surfbits = (BYTE*)desc.lpSurface; BYTE* imagebits = (BYTE*)(&bmpbuf[(h - 1) * bytesgiven]); // Проверить, совпадает ли формат файла с форматом поверхности // Если совпадает, пересылку можно ускорить функцией memcpy() if (loREDbit == 16 && loGREENbit == 8 && loBLUEbit == 0) { TRACE("using optimized code...\n"); for (int i = 0;i < h; i++) { memcpy(surfbits, imagebits, bytesrequired); surfbits += desc.lPitch; imagebits -= bytesgiven; } } else { TRACE("not using optimated code...\n"); for(int i = 0; i < h; i++ ) { RGBTRIPLE* surf = (RGBTRIPLE*)surfbits; RGBTRIPLE* image = (RGBTRIPLE*)imagebits; for (int p = 0; p < w; p++) { DWORD r = image->rgbtRed << loREDbit; DWORD g = image->rgbtGreen << loGREENbit; DWORD b = image->rgbtBlue << loBLUEbit; DWORD* data = (DWORD*)surf; *data = r | g | b; surf++; image++; } surfbits += desc.lPitch; imagebits -= bytesgiven; } } surf->Unlock(0); return TRUE; } |
Функция Copy_Bmp24_Surface24() учитывает две возможные ситуации. Если формат пикселей поверхности совпадает с форматом графических данных, целые строки пикселей копируются в цикле функцией memcpy() без всяких изменений. В противном случае используется второй цикл.
Неоптимизированный цикл похож на тот, что применялся для 16-битных поверхностей, но на этот раз нам не нужно выполнять сокращение цветов. Для доступа к поверхности и графическим данным используются два указателя, surf и image. Оба являются указателями на 24-битный тип RGBTRIPLE, что упрощает перебор 24-битных пикселей.
Каждая цветовая составляющая извлекается из буфера графических данных и сдвигается в соответствии со значением переменных loREDbit, loGREENbit и loBLUEbit. Затем компоненты объединяются и заносятся в память поверхности. Наконец, инкрементирование указателей surf и image перемещает их к следующему пикселю.
Последняя функция, Copy_Bmp24_Surface32(), предназначена для 32-битных поверхностей и очень напоминает функцию Copy_Bmp24_Surface24(). Если бы в 32-битной поверхности все 32 бита использовались для хранения цветовых составляющих, нам пришлось бы выполнять расширение цветов, но так как используется только 24 бита, в этом нет необходимости. Функция Copy_Bmp24_Surface32() приведена в листинге 5.4.
Листинг 5.4. Функция Copy_Bmp24_Surface32() |
BOOL DirectDrawWin::Copy_Bmp24_Surface32(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("Copy_Bmp24_Surface32: Lock() failed\n"); return FALSE; } int bytesrequired = w * 3; int bytesgiven = (bytesrequired + 3) & ~3; BYTE* surfbits = (BYTE*)desc.lpSurface; BYTE* imagebits = (BYTE*)(&bmpbuf[(h - 1) * bytesgiven]); for(int i = 0; i < h; i++ ) { DWORD* surf=(DWORD*)surfbits; RGBTRIPLE* image=(RGBTRIPLE*)imagebits; for (int p = 0;p < w; p++) { DWORD r = image->rgbtRed << loREDbit; DWORD g = image->rgbtGreen << loGREENbit; DWORD b = image->rgbtBlue << loBLUEbit; DWORD* data = (DWORD*)surf; *data = r | g | b; surf++; image++; } surfbits += desc.lPitch; imagebits -= bytesgiven; } surf->Unlock(0); return TRUE; } |
Для работы с пикселями каждой строки используются два указателя, surf и image. Первый является указателем на 32-битный тип DWORD и используется для перебора 32-битных пикселей в памяти поверхности. Второй является указателем на 24-битный тип RGBTRIPLE и используется для доступа к пикселям графических данных. Функция вряд ли нуждается в пояснениях, поскольку она ничем не отличается от своего аналога для 24-битных поверхностей, кроме типа указателя surf и отсутствия оптимизированного варианта цикла.
netlib.narod.ru | < Назад | Оглавление | Далее > |