Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

GMSAPR

.pdf
Скачиваний:
11
Добавлен:
16.03.2016
Размер:
9.01 Mб
Скачать

91

Класс CRaster содержит два метода DrawBitmap, выполняющих вывод изображения на контекст устройства. Аргументы одного из методов позволяют задать положение и размеры выводимой области исходного изображения и определить область назначения. По умолчанию изображение выводится полностью в масштабе 1:1, однако с помощью аргументов этой функции можно и изменить масштаб. Второй метод позволяет просто указать позицию начала вывода и масштаб, в котором должно быть нарисовано изображение. Оба метода внутри используют мощную API-функцию StretchDIBits(). Начиная с Windows 98, реализация этой функции умеет выводить растровые данные в форматах JPEG и PNG. Класс CDC, который мы обычно используем для рисования, имеет похожий метод CDC::StretchBlt(), но он в качестве исходного изображения просит указать контекст устройства, а не указатель на данные.

Режим масштабирования выбирается CDC-методом int

SetStretchBltMode( int nStretchMode )

Аргумент функции — iStretchMode — режим масштабирования. Поддерживаются следующие режимы масштабирования:

BLACKONWHITE — выполняет булеву операцию AND между цветом существующих и удаленных пикселов (при уменьшении размера изображения). Этот режим используется, если масштабируется рисунок «черным по белому», то есть алгоритм масштабирования будет стараться сохранить черные пикселы;

COLORONCOLOR — этот режим удаляет (добавляет) строки (столбцы) пикселов без каких-либо попыток сохранить содержащуюся в них информацию. Наиболее быстрый режим. Используется, когда необходимо сохранить цвета изображения неизменными;

WHITEONBLACK — выполняет булеву операцию OR. Этот режим используется, если масштабируется рисунок «белым по черному»;

92

HALFTONE — преобразует изображение к заданному размеру и при этом трансформирует цвета так, чтобы средний цвет полученной картинки приближался к исходному цвету. Наиболее медленный режим. Однако, масштабированная картинка выглядит

лучше за счет сглаживания «лестничного эффекта».

При масштабировании фотографий и цветных рисунков в большинстве случаев наиболее подходящим является режимы COLORONCOLOR и HALFTONE. Далее мы рассмотрим на практике различия между этими режимами.

Метод GetHistogram() предназначен для получения гистограммы яркости изображения. О том, что это такое и зачем "оно" нужно, мы поговорим дальше.

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

4.5Модификация класса документа для обеспечения работы с изображениями

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

Порядок работы с двумя объектами CRaster в этом случае будет выглядеть следующим образом.

93

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

Может случиться так, что пользователю не понравится то, как мы изменили его картинку, тогда он отдает команду «Отменить преобразования». Легко — просто меняем объекты местами.

Итак, заведем в классе нашего документа CBMDoc пару объектов

CRaster, которые и будут хранить изображения:

CRaster m_BM[2]; // два буфера для изображений

CRaster *m_pCurBM; // указатель на активный буфер

Указатель m_pCurBM будет хранить адрес текущего изображения, его-то мы и будем показывать.

Для загрузки изображения переопределим метод OnOpenDocument()

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

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

BOOL CBMDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

if (!CDocument::OnOpenDocument(lpszPathName)) return FALSE;

// Загружаем в первый буфер if(m_BM[0].LoadBMP(lpszPathName))

{

94

m_pCurBM=&m_BM[0];

//Умеем редактировать только RGB888 (RGB24) данные if(m_pCurBM->GetBMInfoPtr()-> bmiHeader.biBitCount!=24)

m_bEditable=FALSE; else

m_bEditable=TRUE;

return TRUE;

}

return FALSE;

}

Как вы можете видеть из листинга, изображение загружается в первый из объектов CRaster. Этот объект становится текущим, его адрес запоминаем в переменной m_pCurBM. Далее проверяем формат цвета изображения. Если он не равен RGB888, то ставим флажок m_bEditable в значение FALSE. Это вовсе не означает, что картинки с отличающимся от RGB888 форматом цвета не будут показываться нашей программой, просто те функции по редактированию изображений, которые мы добавим далее, будут ориентированы на работу с RGB888, а флаг m_bEditable будет предостерегать от их неправильного использования. Если у вас появится желание редактировать не RGB888-картинки, то вам придется либо переделать функции преобразований, либо конвертировать картинки в формат RGB888 на этапе загрузке из файла.

4.6Заключение

Вданной главе мы разобрались, каким образом хранятся растровые рисунки в BMP-формате и рассмотрели его структуру. Рассмотрели создание

MDI-приложения в Microsoft Visual C++ 2005 .NET и реализовали класс

CRaster. Благодаря нему, теперь любое приложение сумеет экспортировать свои рисунки в растровый формат BMP. Рисунки сохраняются в том виде, в

95

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

В реальных программах, как правило, не имеет смысла писать собственные функции или классы сохранения/загрузки изображений, достаточно воспользоваться платными или бесплатными библиотеками (таких как FreeImage и другие). При использовании классов GDI+ можно воспользоваться встроенными в ОС Windows кодеками (codec – сочетание слов coder и decoder, то есть механизм, умеющий как кодировать, так и декодировать некоторую информацию).

96

5Двойная буферизация и вывод изображения

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

5.1Реализация виртуального экрана в программе BMViewer

Внашем случае изображение может быть выведено на контекст устройства с помощью метода CRaster::DrawBitmap(), внутри же этого метода используется API-функция StretchDIBits(), которая всем хороша, однако работает сравнительно медленно. Поэтому поэксплуатируем идею виртуального экрана. Создадим в программе такой экран, и будем выводить изображение не очень часто, а только тогда, когда изменим изображение. Более часто перерисовывать изображение придется объекту-облику, например, при обработке сообщений прокрутки, при перерисовке окна после перекрытия другими окнами или при изменении размеров окна, в этом случае будем просто копировать изображение из виртуального экрана на контекст устройства дисплея с помощью метода CDC::BitBlt(), который работает относительно быстро.

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

97

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

Недостаток — требуется большое количество памяти под растр виртуального экрана. Допустим, у нас имеется изображение 100 на 100 пикселов, при глубине цвета 32 бита растр виртуального экрана будет занимать 40 Кбайт, а при масштабировании рисунка в 10 раз и создании виртуального экрана такого же размера его растр будет занимать уже 4 Мбайта. Такими темпами легко могут возникнуть проблемы с выводом больших рисунков.

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

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

Недостаток — нам придется предпринять дополнительные действия по обработке сообщений о прокрутке изображения или изменении размеров окна, и каждый раз при поступлении таких сообщений выполнять вывод на виртуальный экран требуемой части изображения. Это может замедлить прокрутку изображений.

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

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

98

5.2 Модификация класса облика

Для реализации виртуального окна заведем в объекте-облике пару переменных:

CBitmap m_VirtScreenBitmap;

CDC m_VirtScreenDC;

В объекте m_VirtScreenBitmap будем хранить растр виртуального экрана, а объект m_virtScreenDC будет контекстом виртуального экрана.

Контекст виртуального экрана должен быть совместим с контекстом окна, в который будет выполняться вывод. Добавим в класс CBMView обработчик сообщения WM_CREATE, в котором и создадим совместимый контекст:

int CBMView::OnCreate( LPCREATESTRUCT lpCreateStruct)

{

if (CScrollView::OnCreate(lpCreateStruct)==-1) return -1;

// Создадим совместимый контекст для виртуального экрана

CPaintDC dc(this); m_VirtScreenDC.CreateCompatibleDC(&dc);

return 0;

}

Теперь нам остается только организовать вывод изображения на виртуальный экран. Для решения этой задачи нам потребуется сделать следующее:

Добавить метод, в котором на виртуальный экран будет выводиться изображение, содержащееся в объекте-документе. Назовем его UpdateVirtualScreen(). В этом же методе будем создавать растр для виртуального экрана, достаточный по размерам для вывода всего изображения.

99

В метод CBMView::OnDraw() добавим копирование виртуального экрана в клиентскую часть окна вывода. Копирование будем выполнять с учетом позиции прокрутки.

Переопределить виртуальный метод OnUpdate() так, чтобы в нем устанавливались размеры области прокрутки, соответствующие размеру изображения с учетом коэффициента масштабирования. Это требуется для того, чтобы пользователь мог, прокручивая изображение в окне, полюбоваться на любую его часть. Добавленный же при создании каркаса приложения метод OnInitialUpdate() можно удалить (но тогда не забыть убрать и объявление этого метода в интерфейсе класса) или просто закомментировать в нем действия по установке размеров области прокрутки. Это можно сделать, так как реализация OnInitialUpdate() в базовом классе вызывает метод

OnUpdate(), который мы уже модифицировали должным образом.

Ещё нужно обязательно переопределить метод-обработчик сообщения WM_ERASEBKGND для того, чтобы самим контролировать перерисовку (очистку) фона окна облика – в переопределенном методе необходимо закомментировать вызов базового метода, оставив лишь return 0. Это позволит избежать мерцания при перерисовке окна облика.

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

// Цвет для заливки фона

#define GRAY RGB(127, 127, 127) BOOL CBMView::UpdateVirtualScreen()

{

CBMDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// Получили указатель на активную картинку

100

CRaster* pCurBM=pDoc->GetCurrentBMPtr(); if(pCurBM==NULL) return FALSE;

// Вычисляем размеры картинки с учетом масштаба

LONG imgw=static_cast<LONG> (pCurBM-> GetBMWidth()*m_dScale); LONG imgh=static_cast<LONG> (pCurBM-> GetBMHeight()*m_dScale);

// Если битмап существует, возьмем ее размер

BITMAP BMStruct;

BMStruct.bmWidth=BMStruct.bmHeight=0;

if(m_VirtScreenBitmap.GetSafeHandle( )) m_VirtScreenBitmap.GetBitmap(&BMStruct);

//Если размеры виртуального экрана

//меньше размеров картинки, увеличим экран

if(BMStruct.bmWidth<imgw || BMStruct.bmHeight<imgh)

{

CPaintDC dc(this);

// Размеры дисплея в пикселах

int scrw=dc.GetDeviceCaps(HORZRES); int scrh=dc.GetDeviceCaps(VERTRES);

//Выберем временную битмап в контексте

//это освободит m_VirtScreenBitmap

//если она была ранее выбрана в контексте

//и даст возможность удалить ее

CBitmap TempBM; TempBM.CreateCompatibleBitmap(&dc,1,1); m_VirtScreenDC.SelectObject(&TempBM);

// Разрушим ранее существовавшую битмап m_VirtScreenBitmap.DeleteObject();

//и на ее месте построим новую, по размерам изображения

//не меньше размеров дисплея

if(!m_VirtScreenBitmap.CreateCompatibleBitmap(&dc,

(imgw<scrw?scrw:imgw), (imgh<scrh?scrh:imgh)))

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]