Растровые изображения

В начало

Основные понятия

Загрузить и вывести на экран изображение, используя функции WinAPI, довольно сложная задача (например, функций по загрузке изображения из файлов вообще нет, а функции манипулирования изображениями в памяти имеют ограниченные возможности). Средства GDI немного упрощают работу с изображениями за счет того, что с каждым DC, предназначенным для графического вывода, связан графический объект bitmap (растровое изображение), который хранит массив пикселей, выводимых на устройство. Однако, для того, чтобы рисовать средствами GDI, придется создавать и хранить изображение в виде графического объекта bitmap.

В общем случае, Windows поддерживает два формата битовых изображений - аппаратно-зависимый DDB (device-dependent bitmap ) и аппаратно-независимый DIB (device-independent bitmap). DDB формат - набор бит в оперативной памяти, структура которого жестко привязана к аппаратным особенностям устройства вывода и, который может быть отображен на этом устройстве (например, при прямом копировании бит в видео память монитора, в память принтера, плотера и. т.д.). Естественно - DDB обеспечивают более быстрый вывод изображений, но универсальность DIB позволила ему стать наиболее используемым (за DDB остались, практически, лишь прошивки тестов для устройств вывода и форматы промежуточных преобразований). Отметим, что кроме битовых изображений, Windows поддерживает и векторные изображения, являющиеся ни чем иным как совокупностью отдельных графических объектов, из которых состоит изображение (формат метафайл Windows с расширением *.emf (*.wmf) хранит картинку в виде набора описаний и определений всех компонент графики и их характеристик - отрезков линий, шаблонов заполнения, текста и его атрибутов и т.п. - или, немного упрощая - набор функций GDI). Векторные изображения масштабируются без потери качества, требуют при хранеии меньше памяти, но проигрывают битовым изображениям в скорости. И, все же, хотя ряд устройств, например плоттер или графопостроитель, способны работать только с векторными изображениями (рисование выполняется перьями), все же основные форматы Windows это DIP и DDP.

В начало

Работа с изображениями формата DDB

Изображения DDB, в силу спицифики их применения, либо загружаются из ресурсов приложения, либо создаются непосредственно в оперативной памяти.

Работе с ресурсами посвящена отдельная глава "Работа с ресурсами в Borland C++ Builder" и, поэтому, здесь эти вопросы подробно рассматриваться не будут.

Отметим лишь, что для загрузке растрового изображения из ресурсов используется функция LoadBitmap():

HBITMAP 
LoadBitmap 
(
 HINSTANCE hInstance,  //дескриптор приложения 
 LPCTSTR lpBitmapName  //имя ресурса
); 	

Функция возвращает идентификатор загруженного изображения или NULL. Имя ресурса - параметр, который содержит идентификатор ресурса, либо идентификатор ресурса в младшем слове и 0 в старшем слове. Сам ресурс, как это показано далее, должен быть включен в приложение через файл определения ресурсов .rc.

В начало

Использование предопределенных растровых изображений

LoadBitmap(), может использовать предопределенные точечные рисунки Win32 API. Их перечень не трудно получить по F1 для функции LoadBitmap(), их много, и здесь перечислены лишь некоторые:

OBM_CHECK      - галочка;
OBM_BTSIZE     - угловая штриховка;
OBM_BTNCORNERS - кружочек;
OBM_CHECKBOXES - совокупность нескольких 
                 элементов управления;
OBM_CLOSE      - пиктограмма Windows Close;
OBM_COMBO      - заштрихованная стрелка вниз;
OBM_DNARROW    - заштрихованная стрелка вниз 
                 на кнопочке (не нажата);
OBM_DNARROWD   - заштрихованная стрелка вниз 
                 на кнопочке (нажата);
OBM_DNARROWI   - стрелка вниз на кнопочке в 
                 режиме Enabled (нажата);
OBM_LFARROW    - заштрихованная стрелка влево на кнопочке;
...........
 и т.д.

Следующий пример показывает использование предопределенных растровых изображений Windows:

Чтобы приложение использовало любую из OBM_ констант, константа OEMRESOURCE должна быть установлена прежде, чем включится заголовочный файл, поэтому в начеле файла unit1.cpp необходимо поместить следующую директиву:

#define OEMRESOURCE

Далее в любом обработчике (Например OnClick для кнопки) пишем код:

void __fastcall
TForm1::Button1Click(TObject *Sender)
{
 HBITMAP hBmp=LoadBitmap(NULL,MAKEINTRESOURCE(OBM_CLOSE));
 if(hBmp)
 {
  HDC     hDc=GetDC(Handle);
  //Устанавливаем прозрачный фон
  SetBkMode(hDc,TRANSPARENT);
  //Создаем контекст в памяти, совместимый с контекстом 
  //устройства - в данном случае окна приложение
  HDC     hMemDc = CreateCompatibleDC(hDc);
  //Выбор битового изображения в контекст памяти 
  SelectObject(hMemDc,hBmp);
  //Отрисовка изображения
  BitBlt(hDc,0,0,24,24,hMemDc,0,0,SRCCOPY);
  StretchBlt(hDc,0,30,48,48,hMemDc,0,0,24,24,SRCCOPY);
  //Возврат к исходному состоянию
  DeleteDC(hMemDc);
  DeleteDC(hDc);
  DeleteObject(hBmp);
 }
}

graphics_1.jpg

Рис 1. Пример использования DDB.

Здесь использованы функции:

Функция SelectObject выбирает растровое изображение в указанный контекст устройства.

HGDIOBJ SelectObject 
(
 HDC     hdc,       // дескриптор контекста устройства 
 HGDIOBJ hgdiobj    // дескриптор растрового изображения  
); 	

Функции отрисовки изображения BitBlt() и StretchBlt() позволяют отрисовать растровое изображение без методом прямого копирования и с масштабированием.

BOOL WINAPI BitBlt
(
 HDC   hdcDest,    // контекст для рисования
 int   nXDest,     // x-координата верхнего левого угла
                   //   области рисования
 int   nYDest,     // y-координата верхнего левого угла
                   //   области рисования
 int   nWidth,     // ширина изображения
 int   nHeight,    // высота изображения
 HDC   hdcSrc,     // идентификатор исходного контекста
 int   nXSrc,      // x-координата верхнего левого угла
                   //   исходной области
 int   nYSrc,      // y-координата верхнего левого угла
                   //   исходной области
 DWORD dwRop       // код растровой операции
);
BOOL WINAPI StretchBlt
(
 HDC hdcDest,      // контекст для рисования
 int nXDest,       // x-координата верхнего левого угла
                   //   области рисования
 int nYDest,       // y-координата верхнего левого угла
                   //   области рисования
 int nWidthDest,   // новая ширина изображения
 int nHeightDest,  // новая высота изображения
 HDC hdcSrc,       // идентификатор исходного контекста
 int nXSrc,        // x-координата верхнего левого угла
                   //    исходной области
 int nYSrc,        // y-координата верхнего левого угла
                   //    исходной области
 int nWidthSrc,    // ширина исходного изображения
 int nHeightSrc,   // высота исходного изображения
 DWORD dwRop       // код растровой операции
);

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

Таким образом, для того, чтобы выводить изображение на экран используется следующая кодовая последовательность. После получения дескриптора изображения выбирается контекст устройства для отображения (DC, функция SelectObject), затем он сопоставляется некоторому контексту созданному в памяти (memory DC), совместимомому с данным контекстом. Далее в этом контексте в объекте default bitmap - растровом изображении, созданным вместе с контекстом memory DC, выполняются необходимые действия по отрисовке изображения и далее функциями копирования битов растровое изображение выводится в DC. В принципе отрисовку можно вести и прямо в DC, но результатом будет мерцание экрана, от которого как раз и избавляют быстрые функции копирования, такие как BitBlt() и StretchBlt(). После использования приложение должно удалить из памяти битовое изображение и освободить задействованные контексты устройств.

Приложение может определить параметры загруженного изображения, вызвав функцию GetObject :

int WINAPI GetObject
(
 HGDIOBJ   hgdiobj,   // идентификатор объекта
 int       cbBuffer,  // размер буфера
 void FAR* lpvObject  // адрес буфера
);

С помощью этой функции можно получить разнообразную информацию об объектах GDI, таких, как логические перья, кисти, шрифты или битовые изображения. Идентификатор изображения должен передаваться через параметр hgdiobj. Параметр lpvObject должен указывать на структуру типа BITMAP, в которую будут записаны сведения об изображении. Через параметр cbBuffer передается размер структуры BITMAP:

BITMAP   bmp;
HBITMAP  hBmp;
GetObject(hBmp,sizeof(BITMAP),(LPSTR) &bmp);

Структура BITMAP определена как:

typedef struct tagBITMAP
{
 int    bmType;
 int    bmWidth;
 int    bmHeight;
 int    bmWidthBytes;
 BYTE   bmPlanes;
 BYTE   bmBitsPixel;
 void   FAR* bmBits;
}BITMAP;

Где:

  • bmType - Тип битового изображения.

  • bmWidth - Ширина битового изображения в пикселах.

  • bmHeight - Высота битового изображения в пикселах.

  • bmWidthBytes Размер памяти, занимаемый одной строкой растра битового изображения.Значение должно быть четным, так как массив изображения состоит из целых чисел размером 16 бит.

  • bmPlanes - Количество плоскостей в битовом изображении. Например, для монохромных битовых изображений используется одна плоскость. Для определения цвета пиксела (черный или белый) используется один бит памяти. Размер памяти, занимаемый одной строкой растра битового изображения, кратен величине 16 бит.

  • bmBitsPixel - Количество битов, используемых для представления цвета пиксела.

  • bmBits - Дальний указатель на массив, содержащий биты изображения.

Используя функцию GetObject() можно осознанно использовать размеры изображения, например в предыдущем примере:

HBITMAP hBmp=LoadBitmap(NULL,MAKEINTRESOURCE(OBM_CLOSE));
BITMAP   bmp;
GetObject(hBmp,sizeof(BITMAP),(LPSTR) &bmp);
.........
BitBlt(hDc,0,0,bmp.bmWidth,bmp.bmHeight,hMemDc,0,0,SRCCOPY);
StretchBlt(hDc,0,30,48,48,hMemDc,0,0,bmp.bmWidth,
                           bmp.bmHeight,SRCCOPY);

В начало

Создание и использование растровых изображений

В предыдущем параграфе мы уже рассматривали пример создания растрового изображения для кисти, повторим пример для отрисовки изображения, немного его продолжив.

 HDC    hDc,hMemDc;
 HBRUSH hBrush,hBrushOld;
 //  Массив для пикеселей изображения  10*10 пикселей. При 24 
 //цветах на каждый пиксель требуется 3 байта или 1.5 unsigned 
 //short числа, для 10*10 пикселей требуется 15*15=225 чисел. 
 //  Создание кистей из битовых образов размером более 8*8
 //пикселей в Win 9x не поддерживается. Если указан битовый
 //образ большего размера, используется его часть.
 unsigned short usrgMassPix[225];
 //Зная как в памяти располагаются биты, например для 24 цветовой
 //палитры синий цвет ff0000, можем задать его как цвет кисти
 if(GetDeviceCaps(CreateDC("DISPLAY",NULL,NULL,NULL),
                                     BITSPIXEL) == 24)
 {
  for(int i=0; i < 75; i++)
  {
   usrgMassPix[i*3]=255;
   usrgMassPix[i*3+1]=0;
   usrgMassPix[i*3+2]=0;
  }
 }
 else //Здесь для других значений или если не знаем как
  for(int i=0; i < 225; i++) usrgMassPix[i] =random(255);
 //Создаем  изображение кисти в памяти 
 //10*10 пикселов  и получаем его хэндл
 HBITMAP
  hBmp = 
   CreateBitmap(10,10,GetDeviceCaps(CreateDC("DISPLAY",
                                       NULL,NULL,NULL),PLANES),
     GetDeviceCaps(CreateDC("DISPLAY",NULL,NULL,NULL),BITSPIXEL),
      &usrgMassPix[0]);
 //Получаем хэндл устройства
 hDc=GetDC(Handle);
 //Создаем кисть
 hBrush = CreatePatternBrush(hBmp);
 //Выбираем кисть в контекст устройства
 hBrushOld=SelectObject(hDc,hBrush);
 //Здесь можно было выбрать перо для обводки фигуры как и 
 //в примере выше, но решили воспользоваться пером по умолчанию
 //Создает контекст совместимый с контекстом устройства
 hDc=CreateDC("DISPLAY",NULL,NULL,NULL);
 hMemDc = CreateCompatibleDC(hDc);
 //Cоздает пустое растровое изображение в памяти размером 50*50
 //совместимое с контекстом устройства и получаем его хэндл
 HBITMAP   hBmpMem = CreateCompatibleBitmap(hDc,50,50);
 //Выбтраем изображение в совместимый контекст в памяти
 SelectObject(hMemDc,hBmpMem);
 RECT rect;
 rect.left = 0;
 rect.top = 0;
 rect.right = 50;
 rect.bottom = 50;
 //Закрашиваем изображение кистью
 FillRect(hMemDc,&rect,hBrush);
 //Выводим изображение или с помощью функции BitBlt
 BitBlt(hDc,0,0,50,50,hMemDc,0,0,SRCCOPY);
 SetBkMode(hDc,TRANSPARENT);
 //Или выводим с помощью функции StretchBlt
 StretchBlt(hDc,0,0,200,200,hMemDc,0,0,50,50,SRCCOPY);
 SelectObject(hDc,hBrushOld);
 DeleteDC(hMemDc);
 DeleteObject(hBmp);
 DeleteObject(hBmpMem);
 DeleteObject(hBrush);
 DeleteObject(hBrushOld);
 ReleaseDC(Handle,hDc);
 DeleteDC(hDc);
}

Функция CreateBitmap() создает точечный рисунок с указанной шириной, высотой, и форматом (цветные панели и число бит на пиксел):

HBITMAP CreateBitmap 
(
 Int   nWidth,        // растровая ширина, в пикселах 
 Int   nHeight,       // растровая высота, в пикселах 
 UINT  cPlanes,       // число цветных плоскостей устройства 
 UINT  cBitsPerPel,   // число бит на пиксель 
 CONST VOID *lpvBits  // указатель на массив пикселей 
); 	

Функция GetDeviceCaps использована для получения информации об устройстве:

Int GetDeviceCaps 
(
 HDC   hdc,    // контекст устройства 
 Int   nIndex  // индекс запроса
);

Для создания изобравжения может быть использована также функция:

HBITMAP CreateBitmapIndirect(BITMAP FAR* lpbm);

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

Другие полезные функции для работы с изображениями DDB:

  • SetBitmapBits(), GetBitmapBits() - позволяют копировать и извлекать массивы бит в/из для созданного изображения.

  • CreateCompatibleBitmap() - создает цветное битовое изображение DDB, формат которого соответствует контексту отображения. Приложение может выбрать неинициализированное изображение, созданное функцией в контекст памяти, используя функции GDI изменить тзображение, передавая им в качестве первого параметра идентификатор контекста памяти. После этого приложение может вызвать функцию BitBlt для отображения результата в окне приложения.

  • CreateDiscardableBitmap() создает удаляемое (discardable) битовое изображение. Если изображение, созданное с использованием этой функции, не выбрано в контекст памяти, оно может быть удалено. При попытке выбрать в контекст удаленное изображение функция SelectBitmap вернет нулевое значение. В этом случае необходимо удалить изображение макрокомандой DeleteBitmap, после чего создать его заново.

В начало

Работа с изображениями формата DIB

Изображения DIB, в отличие от изображений DDB, являются аппаратно-независимыми, поэтому, без дополнительного преобразования, их нельзя отображать на экране с помощью функций BitBlt и StretchBlt. В Windows основным форматом для хранения битовых изображений является формат .bmp и, в тоже время, в программном интерфейсе Windows нет функций, специально предназначенных для рисования битовых изображений этого формата.

Сложности работы с изображениями в формате DIB непосредственно используя функции GDI, о чем речь ниже, привело к тому, что редко кто использует эту возможность, предпочитая работу с использованием свойств комронент Borland C++ Builder. Однако знание формата DIB порой необходимо и полезно, поэтому, кратко остановимся на основном формате DIB - формате bitmap.

Формат bmp-файлов содержит:

  • Блок BITMAPFILEHEADER - структуру описывающую тип файла, его размер, смещение области битов изображения относительно начала файла.

  • Блок BITMAPINFO - структуру, содержащую описание размеров, способ представления цвета в битовом изображении, таблицу цветов и биты изображения.

Структура BITMAPFILEHEADER:

typedef struct 
tagBITMAPFILEHEADER
{
 UINT    bfType;        // Тип изображения, для .bmp файлов BM.
 DWORD   bfSize;        // Размер файла в байтах
 UINT    bfReserved1;
 UINT    bfReserved2;
 DWORD   bfOffBits;     // Смещение начала  непосредственно бит 
                        // изображения относительно начала файла
}BITMAPFILEHEADER;
typedef BITMAPFILEHEADER*      PBITMAPFILEHEADER;
typedef BITMAPFILEHEADER FAR* LPBITMAPFILEHEADER;

Структура BITMAPINFO:

typedef struct tagBITMAPINFO
{
 //Структура BITMAPINFOHEADER
 BITMAPINFOHEADER  bmiHeader;     
 //Массив определяющий цвета каждой 
 //точки изображения
 RGBQUAD           bmiColors[1];  
                                  
} BITMAPINFO;
typedef BITMAPINFO*     PBITMAPINFO;
typedef BITMAPINFO FAR* LPBITMAPINFO;

Биты в массиве bmiColors расположены друг за другом построчно (как ни странно - с конца файла), каждая строка изображения дополняется нулями до границы двойного слова.

Структура BITMAPINFOHEADER содержит информацию относительно измерений и цветного формата растрового изображения:

 
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;        

Размер таблицы цветов (biBitCount) может быть следующий:

Значение   Размер 
 1            2 
 4           16 
 8          256 
24          не используется 

Если содержимое поля biClrUsed отлично от нуля, используется таблица цветов уменьшенного размера. В ней описаны только те цвета, которые содержатся в изображении.

Процесс вывода DIB изображений включает в себя несколько этапов:

  • загрузка bmp-файла в оперативную память;

  • проверка типа изображения, на соответствие формату DIB (BITMAPFILEHEADER);

  • определение размера таблицы цветов и если она есть - ее преобразование в палитру;

  • выборка палитры в контекст отображения и ее реализация;

  • нахождение начала массива бит изображения и его чтение;

  • отрисовка изображения.

Отрисовка изображения может быть выполнена с использованием функции StretchDIBits() (функция StretchDIBits копирует данные для прямоугольной области пикселов формата DIB в область адресата отображения формата DDB, полученное таким образом изображение DDB может быть выбрано в контекст памяти и нарисовано обычным способом при помощи функции BitBlt() или StretchBlt()) или с использованием функции StretchDIBits() (эта функция сама выполняет необходимые преобразования).

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

Работе с графикой, в том числе и с растровыми изображениями, с использованием свойств компонент Borland C++ Builder посвящена следующая глава "Работа с графикой с использованием свойст компонент Borland C++ Builder". В следующем параграфе главы рассмотрим работу со шрифтами.

В начало

Продолжение. Шрифты

В начало главы

В начало раздела

Домой