Разработка кроссплатформенных приложений с использованием Juce

Рисование на канве компонентов

Показывать лекцию целиком

Цель лекции: Научиться создавать основным приёмам работы с графическими примитивами в Juce

Традиционно в программировании канвой называют область компонента, на которой можно рисовать графические примитивы, текст, а также отображать готовые изображения. Каждая точка канвы имеет координаты x и y. Начало системы координат (точка с координатами 0, 0) находится в верхне-левом углу канвы. Координата x возрастает при перемещении слева направо, а координата y — при перемещении сверху вниз. Координаты измеряются в пикселях, наименьших элементах поверхности изображения, с которыми можно манипулировать. Важнейшее свойство пикселя — его цвет.

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

За перерисовку компонента отвечает виртуальная функция virtual void Component::paint(Graphics g), с которой мы уже знакомы по примерам из предыдущих лекций. В классах, наследуемых от Component, эта функция должна быть переопределена для того, чтобы отобразить их содержимое.

До сих пор мы использовали этот метод лишь для того, чтобы производить заливку цветом фона компонента с помощью функции void Graphics::fillAll() const, которая, как мы видим, является методом класса Graphics.

Класс Graphics представляет собой платформ-независимый контекст рисования для прорисовки компонента или изображения. В каждом компоненте Juce есть хотя бы один объект этого класса, который передаётся в функцию virtual void Component::paint(Graphics g) в качестве параметра. Понятно, что класс обладает большим набором методов для рисования фоновых изображений компонентов, не ограничивающихся фоновой заливкой цветом. Он содержит методы для рисования линий, прямоугольников, окружностей и др. Рассмотрим их на простых примерах.

Рисование точек

Для отображения точек используется метод класса Graphics void Graphics::setPixel(int x, int y) const, который закрашивает пиксель с координатами x и y текущим цветом. В листинге 7.1 приведён пример отображения восьми точек, расположенных по кругу (далее в этой главе для краткости в листингах будет приводиться лишь реализация метода paint).

#include "TCentralComponent.h"
//------------------------------------------------------
#include <cmath> // Функции вычисления синуса и косинуса
//------------------------------------------------------
TCentralComponent::TCentralComponent() : Component ("Central Component")
{
  setSize(100, 100);
}
//------------------------------------------------------
TCentralComponent::~TCentralComponent()
{
}
//------------------------------------------------------
void TCentralComponent::paint(Graphics Canvas)
{
  // Фон виджета - белый
  Canvas.fillAll(Colours::white);
  
  // Цвет рисуемых точек - чёрный
  Canvas.setColour(Colours::black);
  
  int iPointsNumber = 8; // Число точек
  // Вычисляем координаты точек
  for(int i = 0; i < iPointsNumber; ++i)
  {
    float fAngle = 2 * float_Pi * i / iPointsNumber;
    int iX = 50 + cos(fAngle) * 40;
    int iY = 50 + sin(fAngle) * 40;
    
    // Рисуем точку
    Canvas.setPixel(iX, iY);
  }
}
//------------------------------------------------------
void TCentralComponent::resized()
{
}
//------------------------------------------------------

Внешний вид работающей программы показан на рисунке 7.1 .

(рис 7.1) Программа, рисующая восемь точек на канве компонента содержимого

Обратите внимание, что для расчёта координат точек мы использовали константу float_Pi из заголовочного файла juce_MathsFunctions.h. В этом же файле объявлена константа double_Pi, отличающаяся от первой, как понятно из названия, точностью числа $$\pi$$.

Рисование линий

Для рисования линий, из одной точки в другую, в классе Graphics реализованы методы void Graphics::drawLine(float startX, float startY, float endX, float endY) const и void Graphics::drawLine(float startX, float startY, float endX, float endY, float lineThickness) const. Отличие между методами в том, что первый рисует текущим цветом линию между точками с координатами startX, startY и endX, endY толщиной в 1 пиксель, а во втором толщину линии можно задать параметром lineThickness. Кроме того, в классе Graphics имеются функции с тем же названием, в которые в качестве параметра можно передавать объекты класса Line, включающие информацию о собственно линии и ряд методов для решения геометрических задач.

С помощью последовательного вызова методов , рис 7.2).

void TCentralComponent::paint(Graphics Canvas)
{
  // Фон виджета - белый
  Canvas.fillAll(Colours::white);
  
  // Цвет рисуемых линий - чёрный
  Canvas.setColour(Colours::black);
  
  int iPointsNumber = 8;
  // Массивы для хранения координат точек, 
  // через которые будут проходить линии
  Array<float> fXs;
  Array<float> fYs;
  // Вычисляем координаты точек
  for(int i = 0; i < iPointsNumber; ++i)
  {
    float fAngle = 2 * float_Pi * i / iPointsNumber;
    int iX = 50 + cos(fAngle) * 40;
    int iY = 50 + sin(fAngle) * 40;
    // и сохраняем их в массивах
    fXs.add((float)iX);
    fYs.add((float)iY);
  }

  for(int i = 0; i < iPointsNumber; ++i)
  {
    if(i != 7)
    {
      // Рисуем линию толщиной 3 пикселя
      Canvas.drawLine(fXs[i], fYs[i], fXs[i + 1], fYs[i + 1], 3.0f);
    }
    else
    {
      // Завершаем многоугольник
      Canvas.drawLine(fXs[7], fYs[7], fXs[0], fYs[0], 3.0f);
    }
  }
}
(рис 7.2) Программа, рисующая многоугольник на канве компонента содержимого

Кроме того, класс Graphics включает метод void Graphics::drawDashedLine(float startX, float startY, float endX, float endY, const float* dashLengths, int numDashLengths, float lineThickness = 1.0f) const, который рисует пунктирную линию по заданным характеристикам. Параметр dashLengths — массив чисел с плавающей точкой, которые содержат информацию о длинах рисуемых отрезках линии и разделяющих пробелов. Например, если в качестве параметра в функцию drawDashedLine будет передан массив {4, 5, 6, 7}, то будет нарисована линия с отрезком в 4 пикселя, пробелом в 5 пикселей, отрезком в 6 пикселей и пробелом в 7 пикселей. Пример использования метода приведён в листинге 7.3; внешний вид работающей программы показан на рисунке 7.3 .

void TCentralComponent::paint(Graphics Canvas)
{
  // Фон виджета - белый
  Canvas.fillAll(Colours::white);
  
  // Цвет рисуемых линий - чёрный
  Canvas.setColour(Colours::black);
  
  int iPointsNumber = 8;
  // Массивы для хранения координат точек, 
  // через которые будут проходить линии
  Array<float> fXs;
  Array<float> fYs;
  // Вычисляем координаты точек
  for(int i = 0; i < iPointsNumber; ++i)
  {
    float fAngle = 2 * float_Pi * i / iPointsNumber;
    int iX = 50 + cos(fAngle) * 40;
    int iY = 50 + sin(fAngle) * 40;
    // и сохраняем их в массивах
    fXs.add((float)iX);
    fYs.add((float)iY);
  }

  // Отрезок линии - 8 пикселей,
  // пробел - 4 пикселя
  float fDashLengths[] = {8, 4};
  for(int i = 0; i < iPointsNumber; ++i)
  {
    if(i != 7)
    {
      // Рисуем линию толщиной 3 пикселя
      Canvas.drawDashedLine(fXs[i], fYs[i], fXs[i + 1], fYs[i + 1], 
            fDashLengths, 2, 3.0f);
    }
    else
    {
      // Завершаем многоугольник
      Canvas.drawDashedLine(fXs[7], fYs[7], fXs[0], fYs[0], 
             fDashLengths, 2, 3.0f);
    }
  }
}
(рис 7.3) Программа, рисующая пунктиром многоугольник на канве компонента содержимого

Рисование прямоугольников

Прямоугольник — одна из самых распространённых геометрических фигур. Для рисования прямоугольников класс Graphics включает специальные методы: void Graphics::drawRect(int x, int y, int width, int height, int lineThickness = 1) const и void Graphics::drawRect(int x, int y, int width, int height, float lineThickness = 1.0f) const. Как можно видеть, разница между методами лишь в последнем параметре: в первой функции толщина линии контура прямоугольника задаётся целым числом, а во второй — числом с плавающей точкой. Понятно, что x и y — это координаты верхне-левого угла прямоугольника, width — его ширина, а height — высота.

Вышеприведённые функции рисуют лишь контур прямоугольника. Для заливки его текущим (заданным) цветом используются методы void Graphics::fillRect(int x, int y, int width, int height) const и void Graphics::fillRect(float x, float y, float width, float height) const. Значения параметров этих методов те же, что и у вышеприведённых функций.

Кроме того, класс Graphics включает одноимённые методы, которые принимают в качестве параметра объект класса Rectangle, хранящий информацию о многоугольнике.

Рассмотрим рисование прямоугольников на простом примере. Задайте размеры компонента содержимого программы равными 120 на 120 пикселей. Нарисуем на его канве два квадрата — красный и, внутри первого, второй, жёлтый. Обведём второй прямоугольник чёрным контуром (, рис 7.4" type="example"/>, void TCentralComponent::paint(Graphics Canvas) { // Фон виджета - белый Canvas.fillAll(Colours::white); // Цвет заливки первого прямоугольника - красный Canvas.setColour(Colours::red); // Рисуем первый прямоугольник Canvas.fillRect(10, 10, 100, 100); // Цвет заливки второго прямоугольника - жёлтый Canvas.setColour(Colours::yellow); // Рисуем второй прямоугольник (внутри первого) Canvas.fillRect(20, 20, 80, 80); // Цвет контура второго прямоугольника - чёрный Canvas.setColour(Colours::black); // Обводим второй прямоугольник контуром // толщиной 3 пикселя Canvas.drawRect(20, 20, 80, 80, 3); } (рис 7.4) Программа, рисующая два квадрата на канве компонента содержимого

Также класс Graphics включает методы для рисования / заливки прямоугольников с закруглёнными углами. Это void Graphics::drawRoundedRectangle(float x, float y, float width, float height, float cornerSize, float lineThickness) const и void Graphics::fillRoundedRectangle(float x, float y, float width, float height, float cornerSize) const, соответственно. Параметр cornerSize задаёт, насколько сильно должны быть закруглены углы прямоугольника. При присвоении параметру нулевого значения углы не будут закруглены. При присвоении ему значения 100 прямоугольник превратится в эллипс. Также имеются перегруженные функции, в которых прямоугольную область можно задавать объектом класса Rectangle.

Перепишем пример из листинга 7.4 с использованием функций , рис 7.4" type="example">листинга 7.4 с использованием функций , void TCentralComponent::paint(Graphics Canvas) { // Фон виджета - белый Canvas.fillAll(Colours::white); // Цвет заливки первого прямоугольника - красный Canvas.setColour(Colours::red); // Рисуем первый прямоугольник Canvas.fillRoundedRectangle(10, 10, 100, 100, 10); // Цвет заливки второго прямоугольника - жёлтый Canvas.setColour(Colours::yellow); // Рисуем второй прямоугольник (внутри первого) Canvas.fillRoundedRectangle(20, 20, 80, 80, 10); // Цвет контура второго прямоугольника - чёрный Canvas.setColour(Colours::black); // Обводим второй прямоугольник контуром // толщиной 3 пикселя Canvas.drawRoundedRectangle(20, 20, 80, 80, 10, 3); } (рис 7.5) Программа, рисующая два квадрата с закруглёнными углами на канве компонента содержимого

Рисование эллипсов

Метод , рис 7.6).

void TCentralComponent::paint(Graphics Canvas)
{
  // Фон виджета - белый
  Canvas.fillAll(Colours::white);
  
  // Цвет заливки эллипса - светло-зелёный
  Canvas.setColour(Colours::lightgreen);
  // Рисуем эллипс
  Canvas.fillEllipse(10, 10, 100, 50);
  
  // Цвет контура эллипса - синий
  Canvas.setColour(Colours::blue);
  // Обводим эллипс контуром 
  // толщиной 3 пикселя
  Canvas.drawEllipse(10, 10, 100, 50, 3);
}
(рис 7.6) Программа, рисующая эллипс на канве компонента содержимого

Рисование произвольных фигур

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

Рассмотрим использование класса на примере рисования сектора, залитого линейным градиентом.

Градиент задаётся объектом класса ColourGradient, конструктор которого принимает следующие параметры: ColourGradient::ColourGradient(const Colour colour1, float x1, float y1, const Colour colour2, float x2, float y2, bool isRadial), где x1 и y1 — координаты первого цвета градиента (colour1), а x2 и y2 — второго (colour2). Флаг isRadial показывает, является ли градиент радиальным (в нашем случае параметр принимает значение false).

Задание заливки градиентом обеспечивает метод класса Graphics void Graphics::setGradientFill(const ColourGradient gradient), принимающий объект класса ColourGradient в качестве параметра.

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

  • void Path::clear() throw() - удаляет все линии из объекта класса Path;
  • void Path::startNewSubPath(float startX, float startY) — начинает новую линию, исходя из параметров, начальной позиции;
  • void Path::quadraticTo(float controlPointX, float controlPointY, float endPointX, float endPointY) — рисует квадратную кривую Безье по заданным координатам;
  • void Path::lineTo(float endX, float endY) — замыкает кривую между её последней точкой и точкой с координатами endX, endY;
  • void Path::closeSubPath() - завершает рисование замкнутой фигуры. Например, если мы рисуем треугольник, следует дважды вызвать метод lineTo, а затем — closeSubPath.
  • Для того, чтобы облегчить расчёт координат фигуры, можно воспользоваться программой the jucer из комплекта поставки библиотеки (см. лекцию 5 ).

    Отрисовку фигуры осуществляет функция ).

    #include "TCentralComponent.h"
    //------------------------------------------------------
    TCentralComponent::TCentralComponent() : Component ("Central Component")
    {
      setSize(200, 200);
    }
    //------------------------------------------------------
    TCentralComponent::~TCentralComponent()
    {
    }
    //------------------------------------------------------
    void TCentralComponent::paint(Graphics Canvas)
    {
      // Фон виджета - белый
      Canvas.fillAll(Colours::white);
      
      // Заливка линейным градиентом от светло-голубого до зелёного цвета
      ColourGradient LinearGradient(Colour(0xff00f7ff), 72.0f, 216.0f,
                Colours::green, 296.0f, 56.0f,
                false);
      Canvas.setGradientFill(LinearGradient);
      
      // Заливаем градиентом сектор -
      // объект класса Path
      Canvas.fillPath(Sector); // Path Sector - объявлен в качестве закрытого 
                // члена класса TCentralComponent
    }
    //-------------------------------------------------------
    void TCentralComponent::resized()
    {
      // Отрисовка фигуры
      Sector.clear();
      Sector.startNewSubPath(84.0f, 28.0f);
      Sector.quadraticTo(172.0f, 60.0f, 140.0f, 156.0f);
      Sector.lineTo(28.0f, 116.0f);
      Sector.closeSubPath();
    }
    //------------------------------------------------------
    

    Внешний вид работающей программы представлен на рисунке 7.7 .

    (рис 7.7) Программа, рисующая сектор с градиентной заливкой на канве компонента содержимого

    Здесь перечислены далеко не все возможности класса Path, однако они достаточно хорошо описаны в документации к библиотеке.

    Вывод текста на канву компонента

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

    Так же, как и при рисовании графических примитивов, цвет текста задаётся методом void Graphics::setColour(const Colour newColour).

    Вывод текста на канву осуществляют два метода:

  • void Graphics::drawSingleLineText(const String text, int startX, int baselineY) const — выводит текст text, расположенный в одну строку, начиная с позиции с координатами startX, baselineY;
  • void Graphics::drawMultiLineText(const String text, int startX, int baselineY, int maximumLineWidth) const — выводит текст text, расположенный в несколько строк, начиная с позиции с координатами startX, baselineY. Параметр maximumLineWidth задаёт максимальную ширину строки в пикселях; часть текста, ширина которой превышает эту величину, переносится на следующую строку.
  • Надписи в обоих методах делаются в соответствии с текущими установками шрифта компонента. Для задания шрифта текста используются методы класса Graphics:

  • void Graphics::setFont(const Font newFont) — принимает в качестве параметра новый объект класса шрифта Font;
  • void Graphics::setFont(float newFontHeight, int fontStyleFlags = Font::plain) — задаёт новые размер и стиль текущего шрифта; гарнитура при этом остаётся неизменной.
  • Пример вывода текста на канву компонента содержимого этими методами представлен в листинге 7.8.

    void TCentralComponent::paint(Graphics Canvas)
    {
      // Фон виджета - белый
      Canvas.fillAll(Colours::white);
      
      // Цвет выводимого текста - чёрный
      Canvas.setColour(Colours::black);
      
      Canvas.drawSingleLineText(tr("Однострочный текст выведен 
                на позицию (10, 30)"), 10, 30);
      
      // Цвет выводимого текста - синий
      Canvas.setColour(Colours::blue);
      Canvas.setFont(Font(25.0000f, Font::bold));
      Canvas.drawMultiLineText(tr("Многострочный текст выведен 
                на позицию (10, 80). 
                \n\n\t\t\t\t\t\t\t\t\tПРИВЕТ, МИР!"), 
                10, 80, 550);
    }
    

    Внешний вид работающей программы представлен на рисунке 7.8 .

    (рис 7.8) Программа, рисующая однострочный и многострочный текст на канве компонента содержимого

    Особенности Juce 2.0

    По сравнению с версией 1.53 метод Graphics::drawDashedLine во второй версии Juce претерпел изменения: теперь в качестве параметров принимаются не числа — координаты начала и конца пунктирной линии, а объект класса Line, предназначенного как раз-таки для хранения информации о линиях:

    void Graphics::drawDashedLine(
                 const Line< float > line, 
                 const float* dashLengths, 
                 int numDashLengths, 
                 float lineThickness = 1.0f, int  	
                 dashIndexToStartFrom = 0) const 
    

    Один из конструкторов класса Line принимает в качестве параметров значения координат начала и конца линии: Line<ValueType>::Line(ValueType startX, ValueType startY, ValueType endX, ValueType endY).

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

    void TCentralComponent::paint(Graphics Canvas)
    {
      // Фон виджета - белый
      Canvas.fillAll(Colours::white);
      
      // Цвет рисуемых линий - чёрный
      Canvas.setColour(Colours::black);
      
      int iPointsNumber = 8;
      // Массивы для хранения координат точек, 
      // через которые будут проходить линии
      Array<float> fXs;
      Array<float> fYs;
      // Вычисляем координаты точек
      for(int i = 0; i < iPointsNumber; ++i)
      {
        float fAngle = 2 * float_Pi * i / iPointsNumber;
        int iX = 50 + cos(fAngle) * 40;
        int iY = 50 + sin(fAngle) * 40;
        // и сохраняем их в массивах
        fXs.add((float)iX);
        fYs.add((float)iY);
      }
    
      // Отрезок линии - 8 пикселей,
      // пробел - 4 пикселя
      float fDashLengths[] = {8, 4};
      for(int i = 0; i < iPointsNumber; ++i)
      {
        if(i != 7)
        {
          // Рисуем линию толщиной 3 пикселя
          Canvas.drawDashedLine(
                Line<float>(fXs[i], fYs[i], fXs[i + 1], fYs[i + 1]), 
                fDashLengths, 2, 3.0f);
        }
        else
        {
          // Завершаем многоугольник
          Canvas.drawDashedLine(
                Line<float>(fXs[7], fYs[7], fXs[0], fYs[0]), 
                fDashLengths, 2, 3.0f);
        }
      }
    }
    

    Краткие итоги

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

    Упражнение

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

    Дополнительные материалы

    Архив с исходными текстами примеров Вы можете скачать здесь

    Вернуться к учебному плану