Программирование — страница 29 из 57

Вывод на экран

“Сначала мир был черным, а затем белым.

а в 1930-х годах появился цвет”.

Папаша Кальвина (Calvin’s dad)[8]


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

Line
,
Lines
,
Polygon
,
Axis
и
Tex
t, являющиеся подклассами класса
Shape
. Объект класса
Shape
хранится в памяти, отображается на экране и допускает манипуляции с ним. В следующих двух главах мы глубже исследуем эти классы. В главе 13 рассмотрим их реализацию, а в главе 14 — вопросы, связанные с проектированием.

12.1. Почему графика?

Почему мы посвящаем четыре главы графике и одну главу — графическим пользовательским интерфейсам (graphical user interface — GUI)? Как никак, эта книга о программировании, а не о графике. Существует огромное количество интересных тем, связанных с программированием, которые мы не обсуждаем и в лучшем случае можем сделать лишь краткий обзор вопросов, касающихся графики. Итак, почему графика? В основном потому, что графика — это предмет, позволяющий исследовать важные вопросы, относящиеся к проектированию программного обеспечения, программирования, а также к инструментам программирования.

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

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

Графические программы очень интересны. Обучение программированию подразумевает чтение множества программ, чтобы получить представление о хорошем коде. Аналогично, для того чтобы хорошо овладеть английским языком, необходимо прочитать много книг, журналов и газет. Благодаря прямой зависимости между тем, что мы видим на экране, и тем, что написано в программе, простой графический код легче для понимания, чем большинство программ, сравнимых с ним по сложности. В этой главе мы начнем читать графические коды практически сразу после введения, а в главе 13 покажем, как написать эти коды за несколько часов.

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

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

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

12.2. Вывод на дисплей

Библиотека ввода-вывода ориентирована на чтение и запись потоков символов. Единственными символами, непосредственно связанными с понятием графической позиции, являются символы перехода на новую строку и табуляции. Кроме того, в одномерный поток символов можно внедрить также понятия цвета и двумерных позиций. Именно так устроены такие языки разметки, как Troff, Tex, Word, HTML и XML (а также связанные с ними графические пакеты). Рассмотрим пример.



Организация


Этот список состоит из трех частей:


  • Предложения , пронумерованные EPddd, ...
  • Пункты , пронумерованные EIddd, ...
  • Предположения , пронумерованные ESddd, ...
  • Мы пытаемся ...


    Это фрагмент кода на языке HTML, в котором указан заголовок

    (

    ...

    ), список
    (
      ...
    ) с пунктами
    (
  • ...
  • ) и параграфы
    (

    ). Мы оставили часть текста, поскольку он в данном случае роли не играет. Главное, что мы можем выразить свое представление о разметке в обычном тексте, а связи между тем, как записаны символы и как они появляются на экране, являются опосредованными и управляются программой, интерпретирующей команды разметки. Такой метод очень прост и чрезвычайно полезен (все, что вы сейчас читаете, было создано с его помощью), но имеет некоторые ограничения.

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

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



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

    12.3. Первый пример

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


    #include "Simple_window.h" // открывает доступ к оконной библиотеке

    #include "Graph.h" // открывает доступ к графической библиотеке

    int main()

    {

      using namespace Graph_lib;  // наши графические средства

                                  // находятся в пространстве

                                  // имен Graph_lib


      Point tl(100,100);          // задаем левый верхний угол экрана


      Simple_window win(tl,600,400,"Canvas"); // создаем простое окно


      Polygon poly;               // создаем фигуру (многоугольник)


      poly.add(Point(300,200));   // добавляем точку

      poly.add(Point(350,100));   // добавляем другую точку

      poly.add(Point(400,200));   // добавляем третью точку


      poly.set_color(Color::red); // уточняем свойства объекта poly


      win.attach (poly);          // связываем объект poly с окном


      win.wait_for_button();      // передаем управление драйверу

                                  // дисплея

    }


    Запустив эту программу, мы увидим примерно такую картину.



    Пройдемся по строкам программы и посмотрим, как она работает. Сначала подставляем в программу заголовочные файлы нашей библиотеки графического интерфейса.


    #include "Simple_window.h" // открывает доступ к оконной библиотеке

    #include "Graph.h"     // открывает доступ к графической библиотеке


    Затем в функции

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

    Затем определяем точку, которую будем считать координатой левого верхнего угла нашего окна.


    Point tl(100,100); // задаем координаты левого верхнего угла экрана


    Затем создаем окно на экране.


    Simple_window win(tl,600,400,"Canvas"); // создаем простое окно


    Для этого мы используем класс

    Simple_window
    , представляющий окно в нашей библиотеке Graph_lib. Конкретный объект класса
    Simple_window
    носит имя
    win
    ; иначе говоря,
    win
    — это переменная класса
    Simple_window
    . Список инициализации объекта win начинается с точки, которая будет использована в качестве левого верхнего угла
    tl
    , за ней следуют числа 600 и 400. Это ширина и высота окна соответственно, измеренные в пикселях. Мы объясним их смысл позднее, а пока лишь укажем, что они позволяют задать прямоугольник с заданными шириной и высотой. Строка
    Canvas
    используется для пометки окна. Если присмотритесь, то увидите слово
    Canvas
    в левом верхнем углу рамки окна.

    Далее помещаем в окно некий объект.


    Polygon poly;             // создаем фигуру (многоугольник)

    poly.add(Point(300,200)); // добавляем точку

    poly.add(Point(350,100)); // добавляем другую точку

    poly.add(Point(400,200)); // добавляем третью точку


    Мы определяем многоугольник poly, а затем добавляем к нему точки. В нашей графической библиотеке объекты класса

    Polygon
    создаются пустыми, мы можем добавить в них любое количество точек, какое пожелаем. Поскольку мы добавили три точки, то получили треугольник. Точки представляют собой простые пары чисел, задающих горизонтальные и вертикальные координаты x и y в окне.

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


    poly.set_color(Color::red); // уточняем свойства объекта poly


    В заключение связываем объект

    poly
    с нашим окном
    win
    .


    win.attach(poly); // связываем объект poly с окном


    Легко заметить, что на экране пока не происходит вообще ничего. Мы создали окно (точнее, объект класса

    Simple_window
    ) и многоугольник (с именем
    poly
    ), окрасили многоугольник в красный цвет (
    Color::red
    ) и связали его с окном
    win
    , но мы не дали команду отобразить это окно на экране. Это делает последняя строка в программе.


    win.wait_for_button(); // передаем управление драйверу дисплея


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

    wait_for_button()
    , которая заставляет систему ждать, пока вы не щелкнете на кнопке Next в окне
    Simple_window
    .

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

    Наше окно выглядит так.



    Обратите внимание на то, что мы немного схитрили. А где же кнопка Next? Мы встроили ее в классе

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

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

    Вы настолько привыкли к тому, что вокруг каждого окна операционная система автоматически рисует рамку, что уже не замечаете ее. Рисунки в этой и следующих главах созданы с помощью системы Microsoft Windows, поэтому в правом верхнем углу каждого окна расположены три кнопки. Они могут быть полезными, если ваша программа зашла в тупик (а это в ходе отладки иногда случается), вы можете прекратить ее выполнение, щелкнув на кнопке со знаком ×. Если вы запустите программу в другой операционной системе, рамка изменится. Наш вклад в оформление рамки заключается лишь в создании метки (в данном случае

    Canvas
    ). 

    12.4. Использование библиотеки графического пользовательского интерфейса

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

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

    Набор инструментов для создания графического пользовательского интерфейса, который мы используем в нашей книге, называется FLTK (Fast Light Tool Kit, произносится как “full tick”) и находится по адресу www.fltk.org. Наш код можно выполнять везде, где выполняется код библиотеки (под управлением операционных систем Windows, Unix, Mac, Linux и др.). Наши интерфейсные классы можно было бы реализовать с помощью другой библиотеки, так что программы стали бы еще более мобильными.

    Модель программирования, представленная в наших интерфейсных классах, намного проще, чем предлагает обычный набор инструментальных средств. Например, наша полная библиотека графических средств и графического пользовательского интерфейса содержит около 600 строк кода на языке С++, в то время как чрезвычайно немногословная документация библиотеки FLTK содержит 370 страниц. Вы можете загрузить ее с веб-сайта www.fltk.org, но мы пока не рекомендуем делать это. Можно вообще обойтись без этой документации. Для создания любого популярного графического пользовательского интерфейса можно использовать идеи, изложенные в главах 12–16. Разумеется, мы объясним, как наши интерфейсные классы связаны с библиотекой FLTK, так что, если захотите, сможете (в конце концов) применить эту библиотеку непосредственно.

     Части нашего “мира графики” можно представить следующим образом.



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

    12.5. Координаты

     Экран компьютера — это прямоугольная область, составленная из пикселей. Пиксель — это маленькая цветная точка. Чаще всего экран в программе моделируется как прямоугольник пикселей. Каждый пиксель имеет горизонтальную координату x и вертикальную координату y. Начальная координата x равна нулю и соответствует крайнему левому пикселю. Ось x направлена направо к крайнему правому пикселю. Начальная координата y равна нулю и соответствует самому верхнему пикселю. Ось y направлена вниз к самому нижнему пикселю.



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

    Количество пикселей зависит от экрана: самыми распространенными являются 1024×768, 1280×1024, 1450×1050 и 1600×1200. В контексте взаимодействия с компьютером окно рассматривается как прямоугольная область экрана, имеющая определенное предназначение и управляемая программой. Окно размечается точно так же, как и экран. В принципе окно можно интерпретировать как маленький экран.

    Например, если программа содержит инструкцию


    Simple_window win(tl,600,400,"Canvas");


    то это значит, что мы хотим создать прямоугольную область, ширина которой равна 600 пикселям, а высота — 400, чтобы адресовать ее от 0 до 599 слева направо и от 0 до 399 сверху вниз. Область окна, которую можно изобразить на экране, называется канвой (canvas). Область 600×400 считается внутренней областью окна, т.е. область, расположенной в системном кадре; она не содержит строки заголовка, кнопок выхода и пр. 

    12.6. Класс Shape

    Наш основной набор инструментов для рисования на экране состоит из двенадцати классов.



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

    Polygon
    может быть использован там, где требуется класс
    Shape
    ; иначе говоря, класс
    Polygon
    является разновидностью класса
    Shape
    .

    Сначала опишем использование следующих классов:

    Simple_window
    ,
    Window

    Shape
    ,
    Text
    ,
    Polygon
    ,
    Line
    ,
    Lines
    ,
    Rectangle
    ,
    Function
    и т.д.

    Color
    ,
    Line_style
    ,
    Point

    Axis


    Позднее (в главе 16) добавим к ним классы графического пользовательского интерфейса:

    Button
    ,
    In_box
    ,
    Menu
    и т.д.


    К этому набору можно было бы более или менее легко добавить много других классов, например

    Spline
    ,
    Grid
    ,
    Block_chart
    ,
    Pie_chart
    и т.д.


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

    12.7. Использование графических примитивов

    В этом разделе мы рассмотрим некоторые элементарные примитивы нашей графической библиотеки:

    Simple_window
    ,
    Window
    ,
    Shape
    ,
    Text
    ,
    Polygon
    ,
    Line
    ,
    Lines
    ,
    Rectangle
    ,
    Color
    ,
    Line_style
    ,
    Point
    ,
    Axis
    . Цель этого обзора — дать читателям представление о том, что можно сделать с помощью этих средств без углубления в детали реализации этих классов. Каждый из этих классов будет подробно изучен в следующих главах.

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

    12.7.1. Графические заголовочные файлы и функция main

    Во-первых, включим заголовочные файлы, в которых определены графические классы и класс графического пользовательского интерфейса.


    #include "Window.h" // обычное окно

    #include "Graph.h"


    или


    #include "Simple_window.h" // если нам нужна кнопка Next

    #include "Graph.h"


    Как вы, возможно, уже догадались, файл

    Window.h
    содержит средства, связанные с окнами, а файл
    Graph.h
    — инструменты, связанные с рисованием фигур (включая текст) в окне. Эти средства определены в пространстве имен
    Graph_lib
    . Для упрощения обозначений мы используем директиву
    using namespace
    , чтобы получить доступ к именам из пространства
    Graph_lib
    .


    using namespace Graph_lib;


    Как обычно, функция

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


    int main ()

    try

    {

      // ...здесь находится наш код...

    }

    catch(exception& e) {

      // сообщения об ошибках

      return 1;

    }

    catch(...) {

      // другие сообщения об ошибках

      return 2;

    }

    12.7.2. Почти пустое окно

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

    main()
    :


    Point tl(100,100); // левый верхний угол нашего окна

    Simple_window win(tl,600,400,"Canvas");

      // координаты окна tl задают положение левого верхнего угла

      // размер окна 600*400

      // заголовок: Canvas

    win.wait_for_button(); // изобразить!


    Этот фрагмент программы создает объект класса

    Simple_window
    , т.е. окно с кнопкой
    Next
    , и выводит его на экран. Очевидно, что для создания объекта класса Simple_window нам необходима директива
    #include
    , включающая в программу заголовочный файл
    Simple_window.h
    , а не
    Window.h
    . Здесь мы указываем, в каком месте экрана должно появиться окно: его левый верхний угол должен находиться в точке
    Point(100,100)
    . Это близко, но не очень близко к левому верхнему углу экрана. Очевидно, что
    Point
    — это класс, конструктор которого получает пару целых чисел и интерпретирует их как пару координат (x, y). Эту инструкцию можно было бы написать так:

    Simple_window win(Point(100,100),600,400,"Canvas"); 

    Однако мы хотим использовать точку (100,100) несколько раз, поэтому удобнее присвоить ей символическое имя. Число 600 — это ширина окна, 400 — его высота, а строка "

    Canvas
    " — метка, которую мы хотим поместить на рамке окна.

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

    win.wait_for_button()
    . Результат показан на следующем рисунке.



     На фоне нашего окна мы видим экран ноутбука (на всякий случай очищенный от лишних пиктограмм). Для любопытных людей, интересующихся деталями, не относящимися к делу, сообщаю, что эту фотографию я сделал, стоя возле библиотеки Пикассо в Антибе и глядя через залив на Ниццу. Черное консольное окно, частично скрытое нашим окном, автоматически открывается при запуске нашей программы. Консольное окно выглядит некрасиво, но позволяет эффективно закрыть наше окно при отладке программы, если мы попадем в бесконечный цикл и не сможем выйти из программы обычным способом. Если внимательно присмотреться, то можно заметить, что мы использовали компилятор Microsoft C++, но вместо него можно было бы использовать любой другой компилятор (например, Borland или GNU).

    Для дальнейшей демонстрации нашей программы мы удалили с экрана все лишнее, оставив только само окно (см. ниже).

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



    12.7.3. Оси координат

    Практически пустое окно не очень интересно, поэтому попробуем добавить в него какую-нибудь информацию. Что бы мы хотели изобразить? Давайте вспомним, что графика — это не только игры и развлечения, и сделаем что-нибудь серьезное и сложное, например оси координат. График без осей координат, как правило, ужасен. Невозможно себе представить, какие данные можно изобразить, не пользуясь осями координат. Может быть, вам удастся оправдать это в приложении к программе, но намного лучше добавить оси координат; люди часто не читают объяснений, а хорошее графическое представление обычно не нуждается в комментариях. Итак, нам необходимы координатные оси.


    Axis xa(Axis::x, Point(20,300), 280, 10, "x axis"); // создаем

      // объект Axis

      // класс Axis — разновидность класса Shape

      // Axis::x означает горизонтальную ось

      // начало оси — в точке (20,300)

      // длина оси — 280 пикселей 10 делений

      // "Ось x" — метка оси

    win.attach(xa);             // связываем объект xa с окном win

    win.set_label("Canvas #2"); // изменяем метку окна

    win.wait_for_button();      // изобразить!


    Последовательность действий такова: создаем объект класса

    Axis
    , добавляем его в окне и выводим на экран.



    Как видим, параметр

    Axis::x
    задает горизонтальную линию. Кроме того, ось имеет десять делений и метку “
    x axis
    ”. Как правило, метка объясняет, что представляет собой ось и ее деления. Естественно, ось х следует выбирать где-то ближе к нижнему краю окна. В реальной программе мы обозначили бы ширину и высоту какими-нибудь символическими константами, чтобы придать фразе “где-то ближе к нижнему краю окна” конкретный смысл, например, выраженный в виде инструкции
    y_max-bottom_margin
    , и не использовали бы “магические константы”, такие как 300 (см. раздел 4.3.1, раздел 15.6.2).

    Для того чтобы идентифицировать результаты, мы изменили метку экрана на строку "

    Canvas #2
    " с помощью функции-члена
    set_label()
    класса
    Window
    .

    Теперь добавим ось

    y
    .


    Axis ya(Axis::y, Point(20,300), 280, 10, "y axis");

    ya.set_color(Color::cyan);           // выбираем цвет

    ya.label.set_color(Color::dark_red); // выбираем цвет текста

    win.attach(ya);

    win.set_label("Canvas #3");

    win.wait_for_button();               // изобразить!


    Просто для того чтобы продемонстрировать некоторые возможности, мы раскрасили ось

    y
    в голубой цвет (cyan), а метку сделали темно-красной.



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

    12.7.4. График функции

    Что дальше? Теперь у нас есть окно с координатными осями, и кажется естественным нарисовать на нем график функции. Создадим фигуру, представляющую график синуса, и свяжем ее с окном.


    Function sine(sin,0,100,Point(20,150),1000,50,50); // график синуса

      // рисуем sin() в диапазоне [0:100) от (0,0) до (20,150),

      // используя 1000 точек; для масштабирования координаты

      // умножаются на 50

    win.attach(sine);

    win.set_label("Canvas #4");

    win.wait_for_button();


    Здесь объект класса

    Function
    с именем
    sine
    рисует график синуса, используя стандартную библиотечную функцию
    sin()
    . Детали построения графиков функций излагаются в разделе 15.3. А пока отметим, что для построения такого графика необходимо выбрать отправную точку (объект класса
    Point
    ), диапазон изменения входных значений, а также указать некоторую информацию, чтобы график поместился в окне (масштабирование).

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



    12.7.5. Многоугольники

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

    Polygon
    задается последовательностью точек, соединенных линиями. Первая линия соединяет первую точку со второй, вторая линия соединяет вторую точку с третьей, а последняя линия соединяет последнюю точку с первой.


    sine.set_color(Color::blue);   // мы изменили цвет графика синуса

    Polygon poly;   // класс Polygon - это разновидность класса Shape

    poly.add(Point(300,200));      // три точки образуют треугольник

    poly.add(Point(350,100));

    poly.add(Point(400,200));


    poly.set_color(Color::red);

    poly.set_style(Line_style::dash);

    win.attach(poly);

    win.set_label("Canvas #5");

    win.wait_for_button();


    На этот раз мы изменили цвет графика синуса (

    sine
    ) просто для того, чтобы показать, как это делается. Затем мы добавили треугольник, так же как в первом примере из раздела 12.3, представляющий собой разновидность многоугольника. Здесь мы также задали цвет и стиль. Линии в классе
    Polygon
    имеют стиль. По умолчанию они сплошные, но их можно сделать пунктирными, точечными и т.п. (подробнее об этом — в разделе 13.5). Итак, мы получаем следующий результат.



    12.7.6. Прямоугольник

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

    Rectangle
    , отделив его от класса
    Polygon
    . Класс
    Rectangle
    характеризуется координатами верхнего левого угла, шириной и высотой.


    Rectangle r(Point(200,200), 100, 50); // левый верхний угол,

                                          // ширина, высота

    win.attach(r);

    win.set_label("Canvas #6");

    win.wait_for_button();


    Этот фрагмент открывает на экране следующее окно.



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

    Rectangle
    еще недостаточно. Легко можно создать объект класса
    Closed_polyline
    , который на экране выглядит как объект класса
    Rectangle
    (можно даже создать объект класса
    Open_polyline
    , который будет выглядеть точно так же).


    Closed_polyline poly_rect;

    poly_rect.add(Point(100,50));

    poly_rect.add(Point(200,50));

    poly_rect.add(Point(200,100));

    poly_rect.add(Point(100,100));

    win.attach(poly_rect);


    Изображение (image) объекта

    poly_rect
    на экране действительно является прямоугольником. Однако объект класса
    poly_rect
    в памяти не является объектом класса
    Rectangle
    и не “знает” ничего о прямоугольниках. Проще всего это доказать, попытавшись добавить новую точку.


    poly_rect.add(Point(50,75));


    Прямоугольник не может состоять из пяти точек.



    Важно понимать, что объект класса

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

    12.7.7. Заполнение

    До сих пор наши фигуры были нарисованы схематично. Их можно заполнить цветом.


    r.set_fill_color(Color::yellow); // цвет внутри прямоугольника

    poly.set_style(Line_style(Line_style::dash,4));

    poly_rect.set_style(Line_style(Line_style::dash,2));

    poly_rect.set_fill_color(Color::green);

    win.set_label("Canvas #7");

    win.wait_for_button();


    Мы также решили, что прежний стиль линии в нашем треугольнике (

    poly
    ) нам не нравится, и изменили его на жирный пунктир (в четыре раза толще обычного пунктира). Аналогично мы изменили стиль объекта
    poly_rect
    (теперь он не выглядит как прямоугольник).



    Если внимательно присмотреться к объекту

    poly_rect
    , то можно увидеть, что он рисуется поверх заполнения.

    Заполнить цветом можно любую замкнутую фигуру (рис. 13.9). Прямоугольники просто весьма удобны для этого. 

    12.7.8. Текст

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

    Text
    .


    Text t(Point(150,150), "Hello, graphical world!");

    win.attach(t);

    win.set_label("Canvas #8");

    win.wait_for_button();



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

    Мы видели, как можно управлять цветом текста: метка оси (см. раздел 12.7.3) просто представляет собой объект класса

    Text
    . Кроме того, мы можем выбирать шрифт и размер символов.


    t.set_font(Font::times_bold);

    t.set_font_size(20);

    win.set_label("Canvas #9");

    win.wait_for_button();


    Здесь мы увеличили буквы в строке "

    Hello, graphical world!
    " до 20 пунктов и выбрали жирный шрифт Times. 

    12.7.9. Изображения

    Мы можем также загружать изображения из файлов.


    Image ii(Point(100,50),"image.jpg"); // файл 400×212 пикселей

                                         // в формате jpg

    win.attach(ii);

    win.set_label("Canvas #10");

    win.wait_for_button();


    Файл

    image.jpg
    — это фотография двух самолетов, преодолевающих звуковой барьер.

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



    ii.move(100,200);

    win.set_label("Canvas #11");

    win.wait_for_button();


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



    12.7.10. И многое другое

    Приведем без объяснений еще один фрагмент кода


    Circle c(Point(100,200),50);

    Ellipse e(Point(100,200), 75,25);

    e.set_color(Color::dark_red);

    Mark m(Point(100,200),'x');

    ostringstream oss;

    oss << "screen size: " << x_max() << "*" << y_max()

    << "; window size: " << win.x_max() << "*" << win.y_max();

    Text sizes(Point(100,20),oss.str());

    Image cal(Point(225,225),"snow_cpp.gif"); // 320×240 пикселей,

                                              // формат gif

    cal.set_mask(Point(40,40),200,150);       // отобразить рисунок

                                              // в центре

    win.attach(c);

    win.attach(m);

    win.attach(e);

    win.attach(sizes);

    win.attach(cal);

    win.set_label("Canvas #12");

    win.wait_for_button();


    Можете ли вы догадаться, что делает этот фрагмент?



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

    istringstream
    (см. раздел 11.4). 

    12.8. Запуск программы

    Мы показали, как можно создать окно и нарисовать в нем разные фигуры. В следующих главах мы покажем, как определен класс

    Shape
    и его подклассы, а также как их использовать.

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

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

    Итак, можно сказать, что наша программа состоит из четырех частей.

    • Код нашей программы (

    main()
    и т.д.).

    • Наша интерфейсная библиотека (

    Window
    ,
    Shape
    ,
    Polygon
    и т.д.).

    • Библиотека FLTK.

    • Стандартная библиотека языка C++.


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



    Как заставить эту программу работать, объясняется в приложении Г.

    12.8.1. Исходные файлы

    Наша библиотека графики и графического пользовательского интерфейса состоит лишь из пяти заголовочных и трех исходных файлов.

    •Заголовки

    •Point.h

    •Window.h

    •Simple_window.h

    •Graph.h

    •GUI.h

    •Исходные файлы

    •Window.cpp

    •Graph.cpp

    •GUI.cpp


    До главы 16 мы можем игнорировать файлы графического пользовательского интерфейса.


    Задание

    Это задание напоминает программу “Привет, мир!”. Его цель — ознакомить вас с простейшими графическими средствами.

    1. Напишите программу, создающую пустой объект класса

    Simple_window
    размером 600×400 пикселей с меткой Мое окно, скомпилируйте ее, отредактируйте связи и выполните. Помните о том, что вы должны подключить библиотеку FLTK, описанную в приложении Г, вставить заголовочные файлы
    Graph.h
    ,
    Window.h
    ,
    GUI.h
    и
    Simple_Window.h
    в ваш код, а также включить в проект файлы
    Graph.cpp
    и
    Window.cpp
    .

    2. Добавьте примеры из раздела 12.7 один за другим, сравнивая их друг с другом.

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


    Контрольные вопросы

    1. Зачем нужна графика?

    2. Почему нельзя обойтись без графики?

    3. Чем графика интересна программисту?

    4. Что такое окно?

    5. В каком пространстве имен находятся наши классы графического интерфейса (наша графическая библиотека)?

    6. Какие графические файлы необходимы для использования графических средств из нашей библиотеки?

    7. Что представляет собой простейшее окно?

    8. Что представляет собой минимальное окно?

    9. Что такое метка окна?

    10. Как задать метку окна?

    11. Что собой представляют экранные, оконные и математические координаты?

    12. Приведите примеры простых фигур, которые можно отобразить на экране.

    13. Какие команды связывают фигуру с окном?

    14. Какие основные фигуры можно использовать для того, чтобы нарисовать шестиугольник?

    15. Как вывести текст в окне?

    16. Как поместить в окне фотографию вашего лучшего друга или подруги? Напишите свою программу.

    17. Представьте, что вы создали объект класса

    Window
    , но на экране ничего не появилось. Перечислите возможные причины.

    18. Представьте, что вы создали объект класса

    Shape
    , но на экране ничего не появилось. Перечислите возможные причины.


    Термины


    Упражнения

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

    Simple_window
    .

    1. Нарисуйте прямоугольник как объект класса

    Rectangle
    и как объект класса
    Polygon
    . Сделайте линии объекта класса
    Polygon
    красными, а линии объекта класса
    Rectangle
    синими.

    2. Нарисуйте объект класса

    Rectangle
    с размерами 100×300 и поместите в него слово “Привет!”.

    3. Нарисуйте ваши инициалы высотой 150 пикселей. Используйте толстую линию. Нарисуйте каждый инициал другим цветом.

    4. Нарисуйте доску для игры в крестики-нолики размером 3×3, чередуя белые и красные квадраты.

    5. Нарисуйте красную рамку шириной один дюйм вокруг прямоугольника, высота которого составляет три четверти высоты вашего экрана, а ширина — две трети ширины экрана.

    6. Что произойдет, если вы нарисуете фигуру, которая не помещается в окне? Что произойдет, если вы нарисуете окно, которое не помещается на экране? Напишите две программы, иллюстрирующие эти эффекты.

    7. Нарисуйте двумерный дом анфас, как это делают дети: дверь, два окна и крыша с дымовой трубой. Детали можете выбрать сами, можете даже нарисовать дымок из трубы.

    8. Нарисуйте пять олимпийских колец. Если помните их цвета, то раскрасьте их.

    9. Выведите на экран фотографию вашего друга. Напишите его имя в заголовке окна и в заголовке внутри окна.

    10. Нарисуйте диаграмму файлов из раздела 12.8.

    11. Нарисуйте ряд правильных многоугольников, вложенных друг в друга. Наиболее глубоко вложенный многоугольник должен быть равносторонним треугольником, вложенным в квадрат, вложенный в пятиугольник, и т.д. Для любителей математики: пусть все точки каждого

    N
    - многоугольника касаются сторон
    (N+1)
    - многоугольника.

    12. Суперэллипс — это двумерная фигура, определенная уравнением



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

    a
    ,
    b
    ,
    m
    ,
    n
    и
    N
    вводятся как аргументы. Выберите
    N
    точек на суперэллипсе, определенном параметрами
    a
    ,
    b
    ,
    m
    и
    n
    . Пусть эти точки лежат на равном расстоянии друг от друга. Соедините каждую из этих
    N
    точек с одной или несколькими другими точками (если хотите, можете задать количество таких точек с помощью дополнительного аргумента или использовать число
    N–1
    , т.е. все другие точки).

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


    Послесловие

     В идеальном проекте каждая сущность непосредственно представляется в программе. Часто мы выражаем наши идеи в виде классов, реальные вещи — в виде объектов классов, а действия и вычисления — в виде функций. Графика — это область, в который эта мысль нашла очевидное воплощение. У нас есть понятия, например окружности и многоугольники, и мы выражаем их в программе в виде классов, например

    Circle
    и
    Polygon
    . Графика отличается от других приложений тем, что, создавая графические программы, программист может сразу видеть объекты классов на экране. Иначе говоря, состояние такой программы непосредственно доступно для наблюдения — в большинстве приложений этой возможности мы лишены. Это непосредственное соответствие между идеями, кодом и выводом делает программирование графики очень привлекательным. Однако помните, что графика — это лишь иллюстрация общей идеи использования классов для выражения основных понятий в виде кода. Эта идея носит намного более общий характер: все наши идеи могут быть выражены в коде либо в виде класса, либо в виде объекта класса, либо в виде совокупности классов.

    Глава 13