Смекни!
smekni.com

Разработка программного обеспечения для фильтрации растровых изображений (стр. 3 из 6)

Листинг 3.2.1 – Обработка команд масштабирования. Файл BMView.cpp

void CBMView::OnViewZoomin()

{// TODO: Add your command handler code here

m_dScale*=2;

OnUpdate(NULL, 0, NULL); }

void CBMView::OnViewZoomout()

{// TODO: Add your command handler code here

m_dScale/=2;

OnUpdate(NULL, 0, NULL); }

void CBMView::OnViewStretchhalftone()

{// TODO: Add your command handler code here

m_nStretchMode=HALFTONE;

OnUpdate(NULL, 0, NULL); }

void CBMView::OnUpdateViewStretchhalftone(CCmdUI* pCmdUI)

{// TODO: Add your command update UI handler code here

pCmdUI->SetCheck(m_nStretchMode==HALFTONE); }

void CBMView::OnViewStretchcoloroncolor()

{// TODO: Add your command handler code here

m_nStretchMode=COLORONCOLOR;

OnUpdate(NULL, 0, NULL); }

void CBMView::OnUpdateViewStretchcoloroncolor(CCmdUI* pCmdUI)

{// TODO: Add your command update UI handler code here

pCmdUI->SetCheck(m_nStretchMode==COLORONCOLOR);}


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

Поскольку данными в нашей программе будут изображения, модифицируем класс документа так, чтобы он умел работать с изображениями. В проект приложения добавим файлы Raster.h, Raster.cpp. В классе документа надо определить данные как объекты класса СRaster. В принципе, для целей показа картинки на экране хватит и одного объекта СRaster. Чтобы наделить программу некоторыми возможностями по редактированию изображений потребуется не один, а, как минимум, два объекта: один для хранения исходной картинки, второй - буфер для приема преобразованной картинки.

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

- Загружаем изображение в первый объект СRaster и показываем его наэкране до тех пор, пока пользователь не даст команду выполнить какие-нибудь изменения изображения.

- Помещаем измененное изображение во второй объект СRaster и начинаем показывать второй объект-картинку.

- Может случиться так, что пользователю не понравится то, как мы изменили его картинку, тогда он отдает команду "Отменить преобразования".При этом меняются объекты местами.

3.4 Программная схема выполнения преобразований. Графические фильтры

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

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

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

Рисунок 3.6.1 - Схема использования фильтров для преобразования изображений


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

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

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

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

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

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

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

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

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

TransformLoop() - сама "фильтрация";

InformAllViews() - передает сообщения всем обликам документа; Рассмотрим метод TransformLoop() (Листинг 3.6.1).

Листинг 3.6.1 – Метод CBMDoc::TransformLoop(). Файл BMDoc.cpp

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;

// Преобразование с использованием текущего фильтра

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. Затем сообщаем текущему фильтру, какое изображение будет исходным, и какое - приемным (адреса объектов CRaster). Далее в цикле прогоняем через фильтр пикселы изображения. На текущий фильтр указывает переменная m_pCurFilter, которую мы завели в классе CBMDoc специально для этих целей. Тип этой переменной - указатель на объект класса CFilter. Преобразование же данных выполняется с помощью метода Cfilter::TransformPix(), Класс СFilter как раз и является базовым для всех фильтров.

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

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

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

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

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

- FALSE - пользователь прервал операцию - не выполняет обновление экрана. Далее им вызывается функция OnStopTimer(), которая разрушает таймер.

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


3.5 Класс “Фильтр”

Выполнение задачи подразумевает существование в программе некоторого объекта-фильтра. Фильтры выполняют разные преобразования, но с точки зрения "фильтрации" они все одинаковы и обращаться с ними она будет единообразно. Поэтому нам надо определить базовый класс CFilter для фильтра с минимальным, но основным набором методов, с помощью которых будет происходить общение. Данные класса - два указателя на объекты-картинки класса Craster:

- m_рSourseBM - адрес объекта "исходная картинка", откуда берутся данные для преобразования;

- m_рDestBM - адрес объекта "приемная картинка", куда помещаются преобразованные данные.

Методы класса:

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

- TransformPix() – преобразует данные одного пиксела с координатами (x,y).

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