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

GMSAPR

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

121

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

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

Общая схема преобразования в этом случае будет выглядеть следующим образом:

1.Пришла команда выполнить преобразование — создаем рабочий поток;

2.Уведомляем объекты-облики о том, что начали преобразование. При этом облик запускает таймер и начинает периодически интересоваться, сколько процентов работы выполнено, показывает пользователю процент выполнения.

3.В рабочем потоке выполняется преобразование и увеличивается процент выполнения.

4.По окончании преобразования (или если пользователь прервал выполнение) в объекты-облики посылаются сообщения о завершении работы и показывается преобразованная картинка.

Поскольку данными в программе BMViewer заведует класс CBMDoc, именно в него и поместим фильтрующую функцию. Для создания рабочего потока потребуется добавить в класс CBMDoc несколько методов:

Transform() — создает рабочий поток;

ThreadProc() — функция потока, запускает фильтрующую функцию для конкретного объекта-документа;

TransformLoop() — сама фильтрующая функция;

122

InformAllViews() — передает сообщения всем обликам документа;

Не будем приводить здесь полный текст этих функций, ограничимся лишь методом TransformLoop():

void CBMDoc::TransformLoop()

{

if(m_pCurFilter==NULL) return; if(!CreateCompatibleBuffer()) return;

m_EventDoTransform.SetEvent(); // выставим флаг

// – идёт преобразование m_bEditable=FALSE; InformAllViews(UM_STARTTRANSFORM);

CRaster *pSBM=GetCurrentBMPtr(), // источник

*pDBM=GetBufferBMPtr(); // приемник m_pCurFilter->SetBuffers(pSBM, pDBM);

for(LONG y=0; y<pSBM->GetBMHeight(); y++)

{// Процент выполнения

InterlockedExchange(&m_lExecutedPercent,

100*y/pSBM->GetBMHeight());

// Проверим, не решили ли прервать преобразование if(!m_EventDoTransform.Lock(0))

{

InformAllViews(UM_ENDOFTRANSFORM, FALSE, 0); m_bEditable=TRUE;

return;

}

LONG x=0;

if( m_bEditHalf ) // Преобразовать только

// половину изображения

{

// Первую половину картинки копируем

123

// в буфер без преобразования x=pSBM->GetBMWidth()/2;

BYTE *pSPix=NULL, *pDPix=NULL; // Указатели на начало строк

if((pSPix=pSBM->GetPixPtr(0,y))!=NULL && (pDPix=pDBM->GetPixPtr(0, y))!=NULL)

//ВНИМАНИЕ! Предполагается,

//что 1 пиксел = 24 бита = 3 байта

memcpy(pDPix, pSPix, 3*x);

}

// Преобразование с использованием текущего фильтра for(; x<pSBM->GetBMWidth(); x++)

m_pCurFilter->TransformPix(x, y);

}

m_EventDoTransform.ResetEvent(); m_bEditable=TRUE;

SwapBM(); // сделать буфер текущим изображением

SetModifiedFlag(); // флаг «данные изменились»

InformAllViews(UM_ENDOFTRANSFORM, TRUE, 0); return;

};

В методе TransformLoop() мы сначала выставляем флаг события

«Выполняется преобразование» — объект m_EventDoTransform класса

CEvent (класс CEvent предназначен для обеспечения синхронизации между потоками). Затем сообщаем текущему фильтру, какое изображение будет исходным, и какое приемным (адреса объектов CRaster). Далее в цикле прогоняем через фильтр пикселы изображения. На текущий фильтр указывает переменная m_pCurFilter, которую мы завели в классе CBMDoc специально для этих целей. Тип этой переменной — «указатель на объект класса CFilter». Преобразование же данных выполняется с помощью метода

124

CFilter::TransformPix(). Класс CFilter как раз и является базовым для всех фильтров (о нем рассказано далее).

В процессе преобразования перед обработкой очередной строки пикселов вычисляется процент выполнения как процент уже обработанных строк изображения. Вычисленное значение записывается в переменную m_lExecutedPercent с помощью API-функции

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

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

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

Посылаемые в облики сообщения о начале и окончании кодирования определены нами в файле BMDoc.h следующим образом:

#define

UM_STARTTRANSFORM

WM_USER+ 0x8000

#define

UM_ENDOFTRANSFORM

UM_STARTTRANSFORM+1

WM_USER — это специальная константа, начиная с которой (вплоть до значения 0xBFFF) программист может определять сообщения для использования в своем приложении без опасений о том, что они будут конфликтовать с Windows-сообщениями. Однако в документации MSDN сказано, что в диапазоне до 0x7FFF некоторые предопределенные Windows-

125

классы могут использовать значения в своих целях. Мы можем пойти дальше в своей осторожности и определить свои сообщения в диапазоне от 0x8000.

Для обработки наших сообщений в классе-облика потребуется сделать следующее:

1.В интерфейс класса CBMView (файл BMView.h) добавим объявление методов:

afx_msg LONG OnStartTransform(UINT wParam, LONG lParam); afx_msg LONG OnEndTransform(UINT wParam, LONG lParam);

2.В карту сообщений класса CBMView (файл BMView.cpp) добавим макрокоманды:

ON_MESSAGE(UM_STARTTRANSFORM, OnStartTransform)

ON_MESSAGE(UM_ENDOFTRANSFORM, OnEndTransform)

3.Добавим в класс CBMView (файл BMView.cpp) реализацию этих методов:

LONG CBMView::OnStartTransform(UINT wParam, LONG lParam)

{

OnStartTimer(); return 0;

}

LONG CBMView::OnEndTransform(UINT wParam, LONG lParam)

{

OnStopTimer();

if(wParam) // обновим изображение на виртуальном экране

UpdateVirtualScreen(); return 0;

}

Как видно из приведенного текста методов, при получении сообщения UM_STARTTRANSFORM в объекте-облике вызывается метод

OnStartTimer(). Этот метод создает таймер. Для обработки сообщений

126

WM_TIMER, которые начнет посылать таймер, в класс CBMView добавили метод OnTimer(). В этом методе будет выполняться запрос процента выполнения операции и обновляться информация о выполнении. Процент выполнения операции будем показывать в заголовке окна облика. Можно было бы, конечно, вывести индикатор выполнения (progress bar) в строку состояния, но как мы дальше увидим, наша программа позволит одновременно выполнять преобразования в нескольких рисунках, а тогда будет не совсем ясно, как делить единственный индикатор.

Приход сообщения UM_ENDOFTRANSFORM обрабатывается методом OnEndTransform(), который, в зависимости от значения аргумента

wParam:

TRUE преобразование успешно закончено — выполняет обновление экрана;

FALSE — пользователь прервал операцию — не выполняет обновление экрана.

Далее им вызывается функция OnStopTimer(), которая останавливает таймер.

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

127

6.4.2 Класс «Фильтр»

Спроектированная нами структура (см. рис. 5) подразумевает существование в программе некоторого объекта-фильтра. Фильтры выполняют разные преобразования, но с точки зрения фильтрующей функции они все одинаковы, и обращаться с ними она будет единообразно. Поэтому нам надо определить базовый класс CFilter для фильтра с минимальным, но основным набором методов, с помощью которых будет происходить общение. Интерфейс такого класса приведен ниже:

class CRaster;

class CFilter // Базовый виртуальный класс

{

protected:

CRaster *m_pSourceBM;

CRaster *m_pDestBM;

public:

// Устанавливает исходное и приемное изображения

void SetBuffers( CRaster *pSource, CRaster *pDest=NULL)

{

m_pSourceBM=pSource; m_pDestBM=pDest;

};

//Виртуальный метод преобразования пиксела

//будет переопределен в производных классах

virtual BOOL TransformPix(LONG x, LONG y){ return FALSE;};

};

Данные класса — два указателя на объекты-картинки класса CRaster:

m_pSourceBM — адрес объекта «исходная картинка», откуда берутся данные для преобразования;

m_pDestBM — адрес объекта «приемная картинка», куда

помещаются преобразованные данные. Методы класса:

128

SetBuffers() — сообщает фильтру адреса исходного и приемного изображения;

TransformPix() — преобразует данные одного пиксела с

координатами (x, y). Должен быть переопределен в производных классах.

Переменная-указатель на этот класс m_pCurFilter заведена в классе

CBMDoc. Этой переменной присваивается адрес текущего фильтра. В классе

CFilter уже объявлены необходимые для фильтрования методы, они и используются в методе CBMDoc::TransformLoop(). Так как метод

CFilter::TransformPix() объявлен виртуальным, в методе

TransformLoop() будет происходить вызов настоящего метода преобразования активного фильтра.

Для реализации точечных методов преобразования создадим класс

CDotFilter:

// Базовый класс для точечных фильтров class CDotFilter: public CFilter

{

protected:

// Таблицы преобразования для компонент цвета

BYTE BGRTransTable[3][256]; public:

// Метод преобразования пиксела

BOOL TransformPix(LONG x, LONG y);

};

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

129

Для точечного фильтра переопределен метод TransformPix(). Он будет общим для большинства рассмотренных далее точечных фильтров. Реализация метода:

BOOL CDotFilter::TransformPix(LONG x, LONG y)

{

BYTE *pDPix=NULL, *pSPix=NULL; if(m_pSourceBM==NULL ) // Источник необходим

return FALSE;

//Если приемник не задан,

//то преобразование помещаем в источник

if(m_pDestBM==NULL) m_pDestBM=m_pSourceBM;

// Получаем указатели на пикселы в источнике и приемнике if((pDPix=m_pDestBM->GetPixPtr(x, y))==NULL ||

(pSPix=m_pSourceBM->GetPixPtr(x, y))==NULL) return FALSE;

// Преобразование: Порядок каналов — BGR *pDPix=BGRTransTable[0][*pSPix]; *(pDPix+1)=BGRTransTable[1][*(pSPix+1)]; *(pDPix+2)=BGRTransTable[2][*(pSPix+2)]; return TRUE;

};

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

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

130

Все, что останется сделать в производных от CDotFilter классах, описывающих разные эффекты, — это реализовать инициализацию таблиц преобразования.

Для реализации пространственных (матричных) методов преобразования создадим класс CMatrixFilter. Интерфейс класса приведен ниже:

// Пространственные (матричные) фильтры

class CMatrixFilter: public CFilter // Базовый класс

{

protected:

int m_rangX; // размер матрицы по X и Y int m_rangY;

const int *m_pMatrix; // указатель на матрицу public:

// Метод преобразования пиксела

BOOL TransformPix(LONG x, LONG y);

};

Данные класса: размер матрицы преобразования и указатель на матрицу. Как правило, используются квадратные матрицы преобразования, но кто знает, может для чего-то будет полезна и не квадратная матрица. Поэтому указывается размер матрицы по горизонтали и вертикали. Размер матрицы определяет зону пикселов, окружающую пиксел (x, y), которая будет вовлечена

врасчет нового значения пиксела (x, y). Указателю на матрицу преобразования m_pMatrix, будет присваиваться адрес матрицы, которая будет использована

впреобразовании. Реализация метода CMatrixFilter::TransformPix():

BOOL CMatrixFilter::TransformPix(LONG x, LONG y)

{

BYTE *pDPix=NULL, *pSPix=NULL;

// Источник и приемник необходимы

if( m_pSourceBM==NULL || m_pDestBM==NULL) return FALSE;

// Определяем зону перекрытия изображения и матрицы

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