Все выпуски  

Visual C++ - расширенное программирование Загрузка изображений GIF, Jpeg из ресурсов и внешних файлов


Информационный Канал Subscribe.Ru

Visual C++ - расширенное программирование

Выпуск № 13
Cайт : SoftMaker.com.ru
Архив рассылки : SoftMaker.com.ru
Количество подписчиков : 4843
В этом выпуске
Анонс
Рассылки сайта progs.biz через subscribe.ru

Новости сайта progs.biz

Подписаться по почте

Краткое описание всех новых уроков и материалов на сайте progs.biz.
Основные направления - языки программирования (Visual C++, Visual Basic.NET, C#, Delphi, Java), WEB-дизайн (HTML, PHP, PhotoShop, SSI), базы данных, обзоры книг по программированию и новых программ.
От автора

Здравствуйте уважаемые подписчики !

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

Как всегда, Вы можете отправить свои пожелания по поводу рассылки и сайта по этому адресу.

Если Вы хотите опубликовать свою статью, исходники, либо создать и вести какой либо раздел в этой рассылке - также пишите мне.

С уважением, Вахтуров Виктор.

MFC - простое и сложное

Добавление функциональности загрузки изображений форматов GIF, Jpeg, EMF (и других) из ресурсов и внешних файлов в свое приложение.

Вступление

Как известно, традиционным и самым широко применяемым графическим форматом ОС Windows является формат BMP. Пожалуй, это один из самых простейших графических форматов вообще. Он проектировался в основном для упрощения программирования и, наверно, поэтому до сих пор поддержка других графических форматов в Win API представлена очень слабо.

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

Стоит отметить, что изображения, хранимые в формате bmp, занимают обычно очень много места. Причина этому - поддержка форматом лишь простейшего RLE - сжатия (только для 4-х и 8-и разрядных индексированных растров).

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

Думаю, многие преимущества других графических форматов очевидны:

  • GIF - поддерживает цветовую палитру переменной длины (до 256 цветов), и, соответственно, только индексированные растры. Главное преимущество - поддержка LZW (Lempel Ziv Welch) - компрессии. Файлы изображений, содержащих малое количество цветов и с низкой энтропией в строках растра, обычно имеют достаточно малый объем.
  • Jpeg - основан на сжатии изображений с потерей качества, но полноцветные изображения (с цветовой глубиной 24 бит/пиксель) получаются достаточно малыми по объему. К тому же при небольшом масштабировании в сторону увеличения без применения интерполяции, их качество не особо резко ухудшается.
  • EMF - его упомяну здесь "за компанию". Формат векторный - фактически представляет собой запись последовательности команд GDI, которые могут быть воспроизведены впоследствии при помощи Win API функций (например, PlayEnhMetaFile). Так как изображение векторное, то его качество при масштабировании не ухудшается (если в метафайл не включены растры). Также, при отсутствии в метафайле растров, он получается достаточно малым по объему.

Наверно, уже стало понятно, что будет разработано сегодня - это достаточно универсальное средство для загрузки изображений в форматах GIF, Jpeg, EMF (а заодно и иконок, курсоров, изображений в BMP формате) из ресурсов приложения и файлов.

Некоторые средства Windows для работы с изображениями

OLE API для работы с изображениями

Здесь я приведу несколько API-функций для оперирования изображениями.

Вот они:
OleLoadPicture
OleLoadPictureEx
OleCreatePictureIndirect

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

Приведу здесь ее прототип:

STDAPI OleLoadPicture(
  IStream * pStream,
               //Pointer to the stream that contains picture's data
  LONG lSize,  //Number of bytes read from the stream
  BOOL fRunmode,
               //The opposite of the initial value of the picture's 
               // property
  REFIID riid, //Reference to the identifier of the interface 
               // describing the type of interface pointer to return
  VOID ppvObj  //Address of output variable that receives interface 
               // pointer requested in riid
);

Эта функция загружает изображение, данные которого получает из потока, представленного com-классом, реализующим интерфейс IStream. Указатель на интерфейс IStream передается в качестве первого параметра. Второй параметр - количество байт, которое должно быть прочитано из потока (или 0, если поток должен читаться целиком).

Параметр fRunmode должен быть равен FALSE, если необходимо сохранить формат загружаемого изображения (фактически этот параметр - инверсное начальное значение свойства KeepOriginalFormat com-объектов, реализующих интерфейс IPicture), или TRUE - в противном случае.

Параметр riid - ссылка на идентификатор com-интерфейса, указатель на который должен быть возвращен в выходном параметре ppvObj (мы будем использовать IID_IPicture для того, чтобы получить указатель на интерфейс IPicture).

Интерфейс IPicture.

В Windows (начиная с Windows 95) специфицирован стандартный com-интерфейс для манипулирования изображениями - IPicture. Этот интерфейс обеспечивает необходимую абстракцию для работы с изображениями, поддерживаемыми GDI: BMP-растры, иконки, метафайлы, и расширенные метафайлы.

Windows предоставляет стандартную реализацию интерфейса IPicture. Объект, реализующий IPicture, также поддерживают интерфейс IPersistStream - поэтому данные объекта можно сохранять и загружать через интерфейс IStream.

И тут следует отметить самый интересный момент - начиная с Windows 98, IPicture позволяет загружать данные форматов GIF и Jpeg.

IPicture содержит основные методы, необходимые для манипулирования изображением:

  • получение основных атрибутов изображения:
    get_Type,
    get_Width,
    get_Height,
    get_Attributes
  • работа с палитрой изображения:
    get_Hpal,
    set_Hpal
  • отрисовка изображения на произвольном контексте устройства:
    Render
Здесь перечислены - не все методы интерфейса IPicture, но их будет достаточно для реализации поставленной задачи.

Реализация требуемой функциональности

Реализация базовой функциональности

Я решил реализовать основной алгоритм загрузки изображений в одной глобальной функции. Функция возвращает дескриптор аппаратно-зависимого растра, который может быть использован в дальнейшем "стандартными" способами (для отрисовки в контексте устройства, добавления в Image List, и.т.д.). Собственно, вся задача и сводится именно к получению этого дескриптора.

Прототип функции написан "в стиле MFC". То есть я передаю туда указатели на объекты классов MFC, а не дескрипторы. Это вносит единообразность и удобочитаемость в код, к тому же избавляя от лишних вызовов Attach - Detach.

Сейчас я просто приведу реализацию этой функции, а потом прокомментирую код - так будет понятней.

HBITMAP VLoadPicture(CFile *pFile, int cxDesired, int cyDesired, CBrush *pBgBrush,
                     CDC *pCompatibleDC)
{
     ASSERT(pFile != NULL);
     ASSERT(pFile->m_hFile != NULL);

     CArchive ar(pFile, CArchive::load | CArchive::bNoFlushOnDelete);

     CArchiveStream ars(&ar);

     LPPICTURE lpPicture = NULL;

     HRESULT hr = OleLoadPicture((LPSTREAM) &ars, 0, FALSE,
                                 IID_IPicture, (void **) &lpPicture);

     ((LPSTREAM) &ars)->Release();

     ar.Close();

     HBITMAP hBmp = NULL;

     if(SUCCEEDED(hr) && (lpPicture != NULL))
     {
          HDC hDC = ::GetWindowDC(NULL);

          if(pCompatibleDC != NULL)
                  hDC = pCompatibleDC->GetSafeHdc();
          else
                  hDC = ::GetWindowDC(NULL);

          if(hDC != NULL)
          {
               CSize szSrc;
               
               if(SUCCEEDED(lpPicture->get_Width((OLE_XSIZE_HIMETRIC *) &szSrc.cx)))
               {
                    if(SUCCEEDED(lpPicture->get_Height((OLE_YSIZE_HIMETRIC *) &szSrc.cy)))
                    {
                         CDC dc;

                         CSize szDest(szSrc);

                         dc.Attach(hDC);
                         dc.HIMETRICtoDP(&szDest);
                         dc.Detach();

                         if(cxDesired > 0)
                                 szDest.cx = cxDesired;

                         if(cyDesired > 0)
                                 szDest.cy = cyDesired;

                         HDC hDCMem = ::CreateCompatibleDC(hDC);

                         if(hDCMem != NULL)
                         {
                              hBmp = ::CreateCompatibleBitmap(hDC, szDest.cx, szDest.cy);

                              if(hBmp != NULL)
                              {
                                   HBITMAP hBmpOld = (HBITMAP) ::SelectObject(hDCMem, hBmp);

                                   ::FillRect(hDCMem, CRect(CPoint(0, 0), szDest),
                                              (pBgBrush != NULL) ?
                                                         (HBRUSH) pBgBrush->GetSafeHandle() :
                                                         ::GetSysColorBrush(COLOR_WINDOW));

                                   HRESULT hRes = lpPicture->Render(hDCMem, 0, 0, szDest.cx,
                                                  szDest.cy, 0, szSrc.cy - 1, szSrc.cx,
                                                  -szSrc.cy, NULL);

                                   ::SelectObject(hDCMem, hBmpOld);

                                   if(FAILED(hRes))
                                   {
                                           ::DeleteObject(hBmp);
                                           hBmp = NULL;
                                   }
                              }

                              ::DeleteDC(hDCMem);
                         }
                    }
               }

               if(pCompatibleDC == NULL)
                       ::ReleaseDC(NULL, hDC);
          }

          lpPicture->Release();
     }

     return hBmp;
}

Итак, сразу отмечу, что источником данных загружаемых изображений является файл.
Первым параметром в функцию передается указатель на объект класса CFile. Подчеркну, что данная функция реализует основную, базовую функциональность, то есть ее "услугами" мы будем пользоваться и в случае загрузки изображений из внешнего файла, и в случае загрузки изображений из ресурсов (да, и в случае загрузки из ресурсов мы будем передавать указатель на файл). Как это будет сделано - смотрите чуть ниже).

Теперь - опишу вкратце остальные параметры:

  • cxDesired, cyDesired - требуемые ширина и высота (соответственно) получаемого изображения. Если какой либо из параметров меньше, или равен нулю, то в качестве него выбирается соответствующий параметр исходного изображения. То есть, указание значений этих параметров (больше 0) позволяет производить масштабирование изображения.
  • pBgBrush - кисть фона. Ей будут закрашены все прозрачные области изображений, если таковые имеются (такое возможно при загрузке курсоров, иконок, изображений в формате GIF, а также метафайлов).
  • pCompatibleDC - контекст устройства, с которым будет "совместим" полученный растр. Это может быть использовано, для реализации принципа WYSIWIG в приложении (можно, например, передать в качестве этого параметра указатель на контекст устройства, полученный для принтера). Если данный параметр равен NULL, то вместо него используется контекст устройства основного дисплея.

Теперь давайте рассмотрим принцип и основные, интересные моменты реализации.

Как было сказано выше, загрузка изображения производится API - функцией OleLoadPicture (которая, в случае успеха, возвратит указатель на интерфейс IPicture, с помощью которого мы впоследствии и получим растр). Основная проблема, стоящая на данном этапе состоит в том, что (об этом также уже упоминалось) данные изображения должны предоставляться приложением через интерфейс IStream.
Я знаю несколько путей решения данной проблемы (например, создание потока в памяти через CreateStreamOnHGlobal, копирование туда данных и потом - использование их для загрузки). Но, к счастью, есть другой - более простой способ.

С состав библиотеки MFC входит класс CArchiveStream. Это - недокументированный класс. Он представляет собой реализацию (частичную) интерфейса IStream. Сам объект класса CArchiveStream не сохраняет никаких данных. Он работает с данными объекта класса CArchive, указатель на который является единственной переменной-компонентой, которую он инкапсулирует.
Декларация CArchiveStream расположена в заголовочном файле AFXPRIV2.H, а реализация методов - в ARCSTRM.CPP.

Сейчас для нас важно то, что:

  • CArchiveStream реализует интерфейс IStream
  • CArchiveStream читает/записывает данные при помощи объекта класса CArchive
  • CArchive читает/записывает данные при помощи объекта класса CFile

Как можно видеть, получилась достаточно стройная цепочка.
Таким образом, мы без особого труда можем связать объект класса CFile с реализацией интерфейса IStream и без проблем использовать данные файла для загрузки изображения через OleLoadPicture:

CArchive ar(pFile, CArchive::load | CArchive::bNoFlushOnDelete);

CArchiveStream ars(&ar);

LPPICTURE lpPicture = NULL;

HRESULT hr = OleLoadPicture((LPSTREAM) &ars, 0, FALSE, IID_IPicture, (void **) &lpPicture);

Далее все достаточно тривиально. Функция создает совместимый контекст устройства (совместимый либо с контекстом, переданным в качестве параметра pCompatibleDC, либо с контекстом основного дисплея), рассчитывает размеры будущего изображения, создает совместимый аппаратно-зависимый растр с этими размерами. Растр закрашивается кистью, переданной как параметр pBgBrush или кистью цвета фона окна (если pBgBrush = NULL).

Потом загруженное изображение отрисовывается на растре с помощью метода Render интерфейса IPicture, полученного при вызове OleLoadPicture:
lpPicture->Render(hDCMem, 0, 0, szDest.cx, szDest.cy, 0, szSrc.cy - 1, szSrc.cx, -szSrc.cy, NULL);

Дескриптор полученного растра возвращается как результат работы функции.

Загрузка изображений (GIF, Jpeg) из внешнего файла

Загрузка изображений из файлов не представляет особых проблем. Достаточно создать объект класса CFile, воспользоваться его методом Open для открытия существующего файла с заданным именем и передать указатель на этот объект в функцию VLoadPicture.

Вот прототип и реализация функции загрузки изображений из файлов:

extern HBITMAP VLoadPicture(LPCTSTR lpszFileName, int cxDesired = 0,
                            int cyDesired = 0, CBrush *pBgBrush = NULL,
                            CDC *pCompatibleDC = NULL);

HBITMAP VLoadPicture(LPCTSTR lpszFileName, int cxDesired, int cyDesired,
                     CBrush *pBgBrush, CDC *pCompatibleDC)
{
     CFile file;

     HBITMAP hRet = NULL;

     if(file.Open(lpszFileName, CFile::modeRead | CFile::shareDenyWrite))
     {
          hRet = VLoadPicture(&file, cxDesired, cyDesired, pBgBrush, pCompatibleDC);
          file.Close();
     }

     return hRet;
}

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

Загрузка изображений (GIF, Jpeg) из ресурсов

В принципе, задача загрузки изображений из ресурсов приложения (ресурсы нестандартных типов) также не является сложной. При помощи функций FindResource и LoadResource можно получить дескриптор блока данных ресурса, а с помощью LockResource - указатель на этот блок. Остается все тот же вопрос - как "привязать" данные в памяти к объекту класса CFile.

К счастью, разработчики MFC позаботились и об этом, включив в библиотеку класс CMemFile (родительским классом которого является класс CFile), позволяющий создавать и использовать "файлы памяти" (фактически, CMemFile предоставляет просто другую реализацию методов класса CFile, манипулирующую блоками памяти, а не организующую запись/чтение данных в файл на диске).

Мы воспользуемся возможностью передать в конструктор объекта указатель на уже созданный блок памяти (можно было также воспользоваться функцией Attach после создания объекта CMemFile). А далее, как и в предыдущем случае, просто вызовем функцию VLoadPicture.

Объявление и реализация функции для загрузки изображений из ресурсов:

extern HBITMAP VLoadPicture(HINSTANCE hInstance, LPCTSTR lpszImageName,
                            int cxDesired = 0, int cyDesired = 0,
                            LPCTSTR lpszResourceType = NULL, CBrush *pBgBrush = NULL,
                            CDC *pCompatibleDC = NULL);


HBITMAP VLoadPicture(HINSTANCE hInstance, LPCTSTR lpszImageName,
                     int cxDesired, int cyDesired, LPCTSTR lpszResourceType,
                     CBrush *pBgBrush, CDC *pCompatibleDC)
{
     // стандартные изображения не грузим

     if((hInstance == NULL) || (lpszImageName == NULL))
          return NULL;

     HRSRC hRSrc = ::FindResource(hInstance, lpszImageName,
                                  (lpszResourceType != NULL) ?
                                       lpszResourceType : "IMG");

     if(hRSrc == NULL)
          return NULL;

     HGLOBAL hData = ::LoadResource(hInstance, hRSrc);

     if(hData == NULL)
          return NULL;

     LPBYTE lpData = (LPBYTE) ::LockResource(hData);

     if(lpData == NULL)
          return NULL;

     CMemFile file(lpData, SizeofResource(hInstance, hRSrc));

     HBITMAP hRet = VLoadPicture(&file, cxDesired, cyDesired,
                                 pBgBrush, pCompatibleDC);

     file.Close();

     return hRet;
}

Кроме уже известных, в функцию передаются параметры:
  • HINSTANCE hInstance - дескриптор модуля, содержащего ресурс изображения.
  • LPCTSTR lpszImageName - имя ресурса изображения (если идентификатор ресурса - числовой, то надо просто использовать макрос MAKEINTRESOURCE).
  • LPCTSTR lpszResourceType - строка-идентификатор типа ресурса. Если она не указана, то тип ресурса будет "IMG" (см. код функции).

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

Демонстрационное приложение

Для демонстрации использования разработанных функций я создал небольшое приложение, которое позволяет загружать и просматривать графические файлы форматов BMP, EMF, WMF, ICO, CUR, GIF, JPEG.

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

Загрузка изображений производится в обработчике OnFileOpen класса CMainFrame:

void CMainFrame::OnFileOpen()
{
     ...

     CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY, (LPCTSTR) strFilter, this);

     dlg.m_ofn.nFilterIndex = m_dwLastFilter;

     if(dlg.DoModal() == IDOK)
     {
          HBITMAP hPicture = VLoadPicture(dlg.GetPathName(), 0, 0,
                             CBrush::FromHandle(::GetSysColorBrush(COLOR_APPWORKSPACE)));

     ...

}

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

Загрузка изображений из ресурсов приложения в демо-примере осуществляется в обработчике сообщения WM_CREATE CMainFrame::OnCreate. В этом обработчике из ресурсов загружаются два изображения - одно в формате GIF (оно будет отображаться элементом управления Static, добавляемым как панель в ReBar - в результате получился неплохой логотип), другое в формате JPEG (оно будет установлено в качестве отображаемой картинки после загрузки приложения). Выглядит это примерно так:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{

     ...

     HBITMAP hPicture = ::VLoadPicture(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_BANNER_GIF),
                                       0, rcToolBar.Height(), "GIF");

     if(hPicture != NULL)
     {
          ...

          m_wndBanner.SetBitmap(hPicture);

          ...
     }

     hPicture = ::VLoadPicture(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_DEFAULT_JPG),
                               0, 0, "JPG");

     if(hPicture != NULL)
     {
          m_wndView.SetBitmap(hPicture);
          m_dwSupportedFormats |= supportedJpeg;
     }

     ...
}

Единственным, что тут стоит отметить, остается то, что я создал в приложении 2 типа ресурсов ("GIF" и "JPG") при импорте графических файлов. В принципе, можно было создать тип ресурсов "IMG" и вообще не указывать его при вызове VLoadPicture, но я решил, что так будет наглядней.

Итоги

Как всегда следует заметить, что в каждой бочке меда присутствует своя ложка дегтя (это, должно быть, фундаментальный закон вселенной). Так и здесь. Уже было указано, что поддержка загрузки изображений форматов GIF и JPEG была введена только в Windows 98. Поэтому, конечно же, не стоит всецело полагаться на приведенные здесь средства при разработке сколь либо серьезного приложения для работы с графикой (в этом случае стоит взглянуть в сторону сторонних библиотек, таких, например как LibJpeg от Independent JPEG Group).

Исходный код демонстрационного приложения и разработанных функций можно найти на странице сайта: группа функций для загрузки изображений в форматах GIF, JPEG, ICO, CUR, EMF, WMF, BMP.

Книги по C++, Visual C++, MFC
Visual C++ и MFC. Руководство для профессионалов.
Visual C++ и MFC. Руководство для профессионалов.

Автор: А. Мешков, Ю. Тихомиров

Книга посвящена объектно-ориентированному программированию для 32-х разрядных систем Windows семейств 9x и NT с использованием компилятора Visual C++ 6 и библиотеки классов MFC версии 4.23.
Книга содержит исчерпывающую информацию о библиотеке классов MFC и методах создания приложений Windows на основе классов этой библиотеки, а также большое количество наглядных и подробно прокомментированных примеров приложений, полные тексты которых приведены на сопроводительной дискете.

Эта книга - для программистов, владеющих языками С и С++ и имеющих опыт программирования в системе Windows

Страница книги на Озоне
Язык программирования Си.
Язык программирования Си.

Автор: Брайан Керниган, Деннис Ритчи

Это - легендарная книга разработчиков языка Си, давно ставшая классикой для всех изучающих и использующих как Си, так и Си++.
Данная книга переработана и дополнена с учетом стандарта ANSI языка C.
Для настоящего третьего русского издания перевод заново сверен с оригиналом, в него внесены некоторые поправки, учитывающие устоявшиеся за прошедшие годы изменения в терминологии, а так же учтены авторские замечания.
Книга будет полезна всем без исключения: программистам, преподавателям, студентам.

Страница книги на Озоне
Рассылки и дискуссионные листы компьютерной тематики
Рассылки
C/C++ Вопрос-Ответ

Это - интерактивная рассылка !
Здесь Вы можете задать свой вопрос по программированию на C и C++, а также ответить на вопросы других подписчиков.

Дискуссионные листы
Программирование. Форум !!!

Самый популярный дискуссионный лист по программированию на subscribe.ru, существующий с момента открытия сервиса дискуссионных листов !

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

Вебстроительство. Форум !!!

В дискуссионном листе ведется обсуждение различных аспектов создания сайтов - написание скриптов, использование, настройка портальных движков, и многое другое. Присоединяйтесь !

Поисковые системы. Форум !!!

Этот дискуссионный лист посвящен обсуждению поиковых систем, методов индексации сайтов поисковиками, способам оптимизации сайта под поисковые системы.

Хостинг. Обзоры и обсуждения платного и бесплатного хостинга.

Вы ищете хостинг (платный, бесплатный) ? Хотите спросить совета в выборе ? Можете обсудить это здесь. Поделитесь советом, если знаете. Или узнайте больше. Все о хостинге.


Всего доброго. До встречи в следующем номере.

http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.soft.prog.qandacpp
Отписаться

В избранное