УДК 629.113.001 

ПОЛЬЗОВАТЕЛЬСКИЙ ИНТЕРФЕЙС ДЛЯ ИЗМЕРИТЕЛЬНЫХ ПРИБОРОВ 

[1]

Национальный исследовательский Иркутский государственный технический университет, 

664074, 3. 

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

Ключевые слова: пользовательский интерфейс; графический движок; алгоритм Брезенхема; алгоритм Ляна-Кнута; ООП; программирование. 

USER INTERFACE FOR MEASUREMENT INSTRUMENTATION

A. Negodyaev

National Research Irkutsk State Technical University,

83 Lermontov St., Irkutsk, 664074

The paper presents the stages of development of user interface for an industrial device. The design stage considers two key points: drawing graphics and user experience with the device. The basic functions for working with the user interface are considered: graphical display (bitmap text) and screening graphics using Bresenham’s line and circle algorithm.

Keywords: user interface, graphics engine, the Bresenham algorithm, the Knuth-Liang (TeX) algorithm, object-oriented programming (OOP), programming

Одной из важных составляющих при работе с любым прибором является интерфейс взаимодействия пользователя [1] и прибора, который бы не только позволял работать с прибором, как это было раньше, но также разрешил бы при минимальных знаниях быстро вникнуть в суть прибора и научиться им пользоваться. Такой подход к построению пользовательских интерфейсов активно используется в мобильных операционных системах, которые выполняют за пользователя большинство его однотипной работы, предоставляя необходимый инструментарий. При создании пользовательского интерфейса такого уровня ставится несколько основных задач, которые должны быть выполнены:

НЕ нашли? Не то? Что вы ищете?

· интуитивно-понятный (простой для понимания пользователя) интерфейс;

· возможность переноса библиотеки на другие процессоры;

· легкость в освоении графической библиотеки программистами;

· инкапсуляция данных – свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе, и скрыть детали реализации от пользователя;

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

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

Класс CGraphics, в данном классе необходимо реализовать следующие простейшие операции работы с графикой:

· вывод пикселя на экран (Pixel);

· вывод линии на экран (использую раннее реализованную функцию вывода пикселя);

· рисование простых геометрических примитивов (овал, прямоугольник);

· реализовать вывод текстовой информации на экран.

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

· Алгоритм DDA-линии – простой алгоритм, использующий вещественную арифметику.

· Алгоритм Брезенхема [2] – оптимизированный алгоритм, использующий целочисленную арифметику и только операции сложения и вычитания.

· Алгоритм Ву – модифицированный алгоритм Брезенхема, обеспечивающий сглаживание.

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

Алгоритм Брезенхема. Отрезок проводится между двумя точками — (x_0, y_0) и (x_1, y_1), где в этих парах указаны колонка и строка, соответственно, номера которых растут вправо и вниз. Сначала мы будем предполагать, что наша линия идёт вниз и вправо, причём горизонтальное расстояние x_1 - x_0 превосходит вертикальное y_1 - y_0, то есть наклон линии от горизонтали — менее 45°. Наша цель состоит в том, чтобы для каждой колонки x между x_0 и x_1, определить, какая строка y ближе всего к линии, и нарисовать точку (x, y).

Общая формула линии между двумя точками:

y - y_0 = \frac{y_1-y_0}{x_1-x_0}(x-x_0).

Поскольку мы знаем колонку , то строка  получается округлением к целому следующего значения:

y = \frac{y_1-y_0}{x_1-x_0}(x-x_0) + y_0.

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

s = \frac{y_1-y_0}{x_1-x_0},

которое можно вычислить заранее. Более того, на каждом шаге мы делаем одно из двух: либо сохраняем тот же y, либо увеличиваем его на 1.

Что из этих двух выбрать – можно решить, отслеживая значение ошибки, которое означает вертикальное расстояние между текущим значением y и точным значением y для текущего x. Всякий раз, когда мы увеличиваем x, мы увеличиваем значение ошибки на величину наклона s, приведённую выше. Если ошибка превысила 0.5, линия стала ближе к следующему y, поэтому мы увеличиваем y на единицу, одновременно уменьшая значение ошибки на 1.

Алгоритм, реализованный на c++

dx = abs(dx);

dy = abs(dy);

if (dx >dy) d = dx;

else d = dy;

x = x1;

y = y1;

Pixel(x, y, 1, color);

for (inti=0; i<d; i++)

{

zxerr + = dx;

yerr + = dy;

if (xerr> d) {

xerr -= d;

x += incx;

}

if (yerr> d) {

yerr -=d ;

y += incy;

}

Pixel(x, y, 1, color);

}

Вывод окружности

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

Алгоритм выполненный в графическом интерфейсе для отрисовки окружности:

int x = 0, y = r, d = 2*(1 - r);

while (y > 0)

{

Pixel(xc+x, yc+y, 1, color);

Pixel(xc+x, yc-y, 1, color);

Pixel(xc-x, yc+y, 1, color);

Pixel(xc-x, yc-y, 1, color);

if ((d + y) > 0)

{

y = y - 1;

d = d - (int)(2*y*1) - 1;

}

if (x > d)

{

x = x + 1;

d = d + 2*x + 1;

}

}

Pixel(xc+x, yc+y, 1, color);

Pixel(xc+x, yc-y, 1, color);

Pixel(xc-x, yc+y, 1, color);

Pixel(xc-x, yc-y, 1, color);

Вывод текстовой информации на экран

Для вывода текста на экран, можно использовать заранее сформированный растр букв [3]. И по высоте и ширине букв определить массивы, какой размерности нам необходимы. К примеру, для вывода буквы высотой восемь пикселей необходимо 8 чисел типа char. Так как один char содержит в себе 8 бит, этого будет достаточно чтобы, вывести символ с размерами 8x8 пикселей. Для того чтобы определить какой пиксель будет закрашен, просматривается каждый бит текущего числа char, если бит имеет ненулевое значение то текущий пиксель закрашивается иначе он будет пропущен. К примеру:

если число char будет равно 69 в шестнадцатеричном (hex) формате число выглядит как 0x69, и в двоичном как 1000101. По данному числу окрашены будут 1, 3, 7 пиксели. Таким образом, к примеру, цифра 0 представленная числами массива как (60, 102, 66, 66, 66, 66, 102, 66):

По данному принципу можно реализовать вывод символа любой ширины и высоты на экран дисплея. После того как реализован алгоритм вывода текста на экран возникает вопрос о правильном переносе слов по слогам. Для этого можно использовать «Алгоритм Ляна-Кнута»[4], предложенного , он применяется в издательской системе TeX. Алгоритм основан на сравнении исходного слова с набором правил (шаблонов). Чем больше правил и чем качественнее они составлены, тем лучше будут расставляться переносы. В пакете TeX можно найти готовые бесплатные наборы правил для многих языков, нужно только внимательно смотреть на условия использования и распространения.

 

Каждое правило состоит из букв и цифр между ними, а также цифр в начале и в конце. Цифру 0 обычно опускают. Например, первое правило должно пониматься как 0п0р0и1. Последовательность букв – это часть слова, для которой определяется перенос, т. е. эта последовательность должна присутствовать в слове. Цифры называют «уровнем», они задают приоритет между правилами и возможность переноса в соответствующей позиции. Четные цифры, включая 0, запрещают перенос. Нечетные – разрешают. Точка в начале правила означает, что правило применяется, только если последовательность находится в начале слова. Аналогично с точкой в конце – слово должно заканчиваться этой последовательностью. Если точка есть и в начале и в конце, то правило содержит слово целиком.

Основные этапы работы алгоритма

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

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

3. Отсечь очевидно недопустимые переносы (например, одна буква в начале или в конце).

Алгоритм нахождения переносов

structpattern_t

{

std::basic_string<unichar>str;

std::vector<unsigned char> levels;

};

structpattern_list_t

{

std::vector<pattern_t*> list;

};

std::vector<unsigned char> levels;

levels. assign(wordString. size(), 0);

for (size_ti = 0; i<wordString. size()-2; ++i)

{

std::vector<pattern_t*>::const_iteratorpatternIter = pattern_list->list. begin();

for (size_t count = 1; count <= wordString. size()-i; ++count)

{

pattern_tpatternFromWord;

patternFromWord. str = bstr(i, count);

if (patternCompare(&patternFromWord, *patternIter))

continue;

patternIter = std::lower_bound(patternIter, pattern_list->list. end(), &patternFromWord, patternCompare);

if (patternIter == pattern_list->list. end())

break;

if (!patternCompare(&patternFromWord, *patternIter))

{

for (size_tlevel_i = 0; level_i< (*patternIter)->levels. size(); ++level_i)

{

unsigned char l = (*patternIter)->levels[level_i];

if (l > levels[i+level_i])

levels[i+level_i] = l;

}

}

}

}

Структура pattern_t – шаблон с полями «последовательности символов» и «набор уровней».

Структура pattern_list_t содержит набор правил.

Класс CGraphicsInterfaces

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

· функции для работы с сенсорной панелью;

· функция для работы с клавиатурой прибора;

· функция для работы с окнами/процессами графического интерфейса.

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

· TouchDown(int x, int y).

· TouchUp(int x, int y).

· TouchMove(int x, int y).

Для обработки клавиш клавиатуры достаточно одной функции, в которую будет поступать код нажатой клавиши:

ButtonPress(intkey)

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

Window->Redraw();

if (ActiveWindow)

{

if(FormStack. size()>0 && FormStack. at(FormStack. size()-1) == Window)

{

FormStack. pop_back();

}

else

{

FormStack. push_back(ActiveWindow);

}

}

ActiveWindow=Window;

DrawInterface().

Функция DrawInterface() занимается непосредственно выводом элементов графического интерфейса на экран.

Для создания всех компонентов, таких как CWindow, CButton, CLabel и далее, необходим базовый класс, содержащий набор виртуальных методов, которые будут вызываться в CGRaphicInterfaces.

Данный класс будет называться CGraphObject с набором следующих методов и свойств:

Left – отступ слева.

Top – отступ сверху.

Width – ширина.

Height – высота.

Focused – объект находится под фокусом (флаг).

NeedToRedraw – объект необходимо отрисовать (флаг).

Visible – объект виден пользователю (флаг).

Draw() – метод вызывающий отрисовку объекта.

Redraw() – метод устанавливающий флаг перерисовки в объект.

CheckTouchDown(x, y) – проверка попадания события Down сенсорной панели по объекту.

CheckTouchUp(x, y) – проверка попадания события Up сенсорной панели по объекту.

CheckTouchMove(x, y) – проверка попадания события Move сенсорной панели по объекту.

OnTouchDown(x, y) – обработчик события Down сенсорной панели.

OnTouchUp(x, y) – обработчик события Up сенсорной панели.

OnTouchMove(x, y) – обработчик события Move сенсорной панели.

ButtonClick(keyButton) – обработчик клавиш клавиатуры,

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

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

Библиографический список

Джеф Раскин. Интерфейс: новые направления в проектировании компьютерных систем. – М.: Символ-Плюс. – 2004.

 Алгоритмические основы машинной графики. – М.: Мир, 1989. – 512 с.

Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн, К. Алгоритмы: построение и анализ. – М.: Вильямс, 2005. – 1296 с.

Никулин геометрия и алгоритмы машинной графики. – СПб.: БХВ-Петербург, 2003. – 560 с.

[1] , студент факультета Кибернетики, e-mail: *****@***ru

 Negodiayev Alexey, a student of Cybernetics Faculty, e-mail: *****@***ru