Книга: Графика для Windows средствами DirectDraw

BMP-файлы

BMP-файлы

Теперь мы знаем, как получить прямой доступ к поверхности и что делать с ее памятью, чтобы изменить значения отдельных пикселей. Давайте используем полученные знания на практике. В этой главе мы напишем приложение DirectDraw для просмотра графических файлов формата BMP. Но перед тем как браться за такую программу, необходимо научиться загружать BMP-файлы.

Формат BMP-файлов

BMP — стандартный формат графических файлов Windows. Подавляющее большинство BMP-файлов хранится без сжатия, что облегчает работу с ними. Даже в сжатых BMP-файлах нет ничего особенно сложного, но мы ограничимся файлами без сжатия.

BMP-файлы состоят из трех основных частей:

• заголовок;

• палитра;

• графические данные (значения пикселей).

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

Палитра присутствует только в BMP-файлах, содержащих палитровые изображения (с глубиной пикселей 8 бит и менее). К 8-битным изображениям прикладывается палитра, состоящая из не более чем 256 элементов.

Графические данные — это и есть само изображение. Формат этих данных зависит от глубины пикселей. Хотя BMP-файлы делятся на несколько типов, мы ограничимся 8-битными и 24-битными изображениями. 8-битные BMP-файлы будут использоваться для работы с 8-битными поверхностями, а 24-битные — для беспалитровых поверхностей. Хотя, по слухам, в природе существуют 16-битные и 32-битные BMP-файлы, они встречаются очень редко — например, мне таковые ни разу не попадались. Впрочем, это не имеет особого значения, так как 24-битную графику можно легко преобразовать в 16- или 32-битный формат.

Структура заголовка

Данные заголовка BMP-файла хранятся в двух структурах: BITMAPFILEHEADER и BITMAPINFOHEADER. Структура BITMAPFILEHEADER присутствует в начале любого BMP-файла и содержит информацию о самом файле. Для нас в этой структуре представляет интерес лишь одно поле — bfType, сигнатура BMP-файла (информацию об остальных полях можно найти в справочной системе Visual C++). В BMP-файлах это поле содержит буквы BM (обе буквы — прописные). По содержимому этого поля мы будем убеждаться в том, что выбранные файлы действительно имеют формат BMP.

Структура BITMAPINFOHEADER содержит информацию об изображении, хранящемся в BMP-файле. Эта структура объявляется так:

typedef struct tagBITMAPINFOHEADER {
 DWORD biSize;
 LONG  biWidth;
 LONG  biHeight;
 WORD  biPlanes;
 WORD  biBitCount;
 DWORD biCompression;
 DWORD biSizeImage;
 LONG  biXPelsPerMeter;
 LONG  biYPelsPerMeter;
 DWORD biClrUsed;
 DWORD biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

Первое поле, biSize, определяет размер структуры BITMAPINFOHEADER в байтах. Если ваша программа создает BMP-файл, это поле заполняется тривиально — достаточно определить размер структуры функцией sizeof. Однако при чтении BMP-файла по содержимому этого поля приходится рассчитывать позицию файла, на которой структура заголовка кончается. Эта мера обеспечивает обратную совместимость, благодаря ей Microsoft в будущем сможет увеличить размер структуры BITMAPINFOHEADER, не нарушая работы существующих приложений.

СОВЕТ

Лучше молчать и прослыть глупцом…

Когда я только начал программировать для Windows, то не понимал, зачем в некоторые структуры включаются поля с их размерами. Забыв о мудром совете Авраама Линкольна, я высказался на эту тему в одной из ранних статей и был справедливо наказан. Впрочем, если бы все прислушались к совету Линкольна, никто бы не писал книг.

Поля biWidth, biHeight и biBitCount определяют размеры изображения. Содержимое поля biCompression позволяет узнать, хранится ли изображение в сжатом виде. Поскольку мы не собираемся работать со сжатыми BMP-файлами, необходимо проверить, имеет ли это поле значение BI_RGB (а не BI_RLE8, свидетельствующее о сжатии файла).

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

Наконец, поле biClrUsed определят количество цветов в палитре (для палитровых изображений). Как и поле biSizeImage, оно часто может быть равно нулю. Это означает, что палитра содержит максимальное количество элементов (256 для 8-битных изображений). Остальные поля структуры BITMAPINFOHEADER не представляют для нас интереса, поэтому я не стану утомлять вас их обсуждением. 

Палитра 

Палитра в BMP-файлах хранится в виде списка структур RGBQUAD, где каждый элемент представляет отдельный цвет. Структура RGBQUAD объявляется так:

typedef struct tagRGBQUAD {
 BYTE rgbBlue;
 BYTE rgbGreen;
 BYTE rgbRed;
 BYTE rgbReserved;
} RGBQUAD;

В первых трех полях хранятся цветовые RGB-составляющие. На поле rgbReserved мы не будем обращать внимания (предполагается, что оно равно нулю). Как я упоминал выше, количество структур RGBQUAD в BMP-файле определяется полем biClrUsed. Тем не менее обычно 8-битные BMP-файлы содержат 256 структур RGBQUAD. В 24-битных RGB-файлах структуры RGBQUAD отсутствуют. 

Графические данные 

Графические данные в основном представляют собой список пикселей, из которых состоит изображение. Однако каждая горизонтальная строка пикселей должна занимать блок памяти, выровненный по границе параграфа. Следовательно, если количество байт, необходимых для хранения строки пикселей, не кратно четырем, в каждую строку включается от одного до трех дополняющих байт.

При этом для работы с графическими данными BMP-файлов используется концепция шага, упоминавшаяся выше в этой главе. Отличие состоит в том, что для графических данных BMP-файлов значение шага вам придется рассчитать самостоятельно. Впрочем, это не так уж сложно, потому что шаг всегда попадает на ближайшую границу параграфа за концом блока памяти, необходимого для хранения строки пикселей.

Изображения хранятся в BMP-файлах в перевернутом виде, так что первая строка пикселей файла на самом деле является нижней строкой настоящего изображения. Чтобы восстановить нормальное изображение, мы начнем чтение файла с последней строки пикселей и будем двигаться к началу. 

Организация доступа к поверхностям 

В наших программах чтением BMP-файлов занимается класс DirectDrawWin. Впервые эта возможность была использована в главе 3, где в программе Bounce BMP-файл загружался на поверхность. То же самое происходит и в программе BmpView, но сначала давайте рассмотрим соответствующий программный код.

Поддержка работы с BMP-файлами в классе DirectDrawWin обеспечивается функцией CreateSurface(). Существуют две версии CreateSurface(): первая в качестве аргументов получает параметры поверхности, а вторая — имя BMP-файла. Вторая версия CreateSurface() загружает BMP-файл, затем создает новую поверхность, параметры которой совпадают с параметрами изображения, и копирует содержимое файла на поверхность. 

Функция 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 surfacen");
  return 0;
 }
 ifstream bmp(filename, ios::binary | ios::nocreate);
 if (!bmp.is_open()) {
  TRACE("LoadSurface: cannot open Bmp filen");
  return 0;
 }
 BITMAPFILEHEADER bmpfilehdr;
 bmp.read((char*)&bmpfilehdr, sizeof(bmpfilehdr));
 char* ptr=(char*)&bmpfilehdr.bfType;
 if (*ptr!='B' || *++ptr!='M') {
  TRACE("invalid bitmapn");
  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 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.

Указатель на поверхность (surfbits) инициализируется значением поля lpSurface. Этот указатель используется для обращений к памяти поверхности. Указатель на графические данные (imagebits) инициализируется адресом последней строки пикселей BMP-файла, поскольку в формате BMP изображение хранится в перевернутом виде.

Затем мы в цикле перебираем все строки пикселей изображения. Благодаря тому, что формат графических данных BMP-файла совпадает с форматом поверхности, для копирования можно воспользоваться функцией memcopy(). Для поверхностей остальных типов такая удобная возможность часто отсутствует. Поле lPitch определяет смещение для указателя на поверхность при переходе к следующей строке. Вспомните, что в этом поле хранится шаг поверхности, который может не совпадать с ее шириной. Целая переменная bytesgiven аналогичным образом используется для перехода к следующей строке буфера графических данных. Поскольку чтение начинается с конца буфера, указатель imagebits уменьшается с каждой очередной итерацией.

Наконец, мы вызываем функцию Unlock() интерфейса DirectDrawSurface и в качестве аргумента передаем ей ноль. С помощью этого аргумента можно сбалансировать вызовы Lock() и Unlock() при многократной блокировке одной поверхности. Для сценариев с однократной блокировкой (включая наш) можно просто передать ноль. 

16-битные поверхности

Загрузка 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() failedn");
  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 инкрементируются для перехода к следующему пикселю. 

24-битные поверхности

Мы рассмотрели доступ к 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() failedn");
  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 перемещает их к следующему пикселю. 

32-битные поверхности

Последняя функция, 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() failedn");
  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 и отсутствия оптимизированного варианта цикла.

Оглавление книги

Оглавление статьи/книги

Генерация: 0.032. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз