English 中文 Español Deutsch 日本語 Português
preview
Использование класса CCanvas в MQL приложениях

Использование класса CCanvas в MQL приложениях

MetaTrader 5Примеры | 15 февраля 2022, 11:14
2 444 1
Mihail Matkovskij
Mihail Matkovskij

В данной статье рассмотрено устройство класса CCanvas, а также использование его в MQL приложениях. Для закрепления полученных знаний рассмотрены практические примеры.


Графика в приложениях

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


Canvas в MQL приложениях

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

С помощью класса CCanvas можно реализовать все возможности десктопных UI. Любые идеи, которые только могут прийти в голову программисту. Помимо пользовательских интерфейсов с помощью CCanvas можно рисовать в чарте буферы индикаторов, бары по ценам Open, Close, Low и High и многое другое... Но это не является темой для обсуждения в рамках данной статьи. Более подробно данная тема будет освещаться в других моих статьях.


Разбор CCanvas

Итак, мы подошли к самому интересному - разбору класса CCanvas. От понимания его устройства будет зависеть ваше дальнейшее освоение этого инструмента. Рассмотрим устройство CCanvas на схеме.

Разбор CCanvas

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

Рассмотрим данные:

  • Пиксельные данные. Представляют собой массив пикселей m_pixels, в который сохраняются пиксели, обрабатываемые методами рисования, также данные m_width и m_height – ширина и высота изображения, соответственно. Параметры m_width и m_height являются очень важными для работы с массивом m_pixels  методов рисования, а также для передачи массива m_pixels графическому ресурсу, в методе Update.
  • Данные шрифта для вывода текста. Состоят из полей m_fontname, m_fontsize, m_fontflags и m_fontangle – название шрифта, размер шрифта, атрибуты шрифта и угол наклона текста, соответственно. Эти данные нужны для установки и получения свойств шрифта (с помощью методов чтения/записи свойств шрифта) перед вызовом метода TextOut.
  • Данные для рисования линий. Состоят из двух полей: m_style - стиль линий и m_style_idx - текущий индекс бита в шаблоне стиля линий. Нужны для работы методов рисования.
  • Данные по умолчанию. Здесь представлено всего одно поле m_default_colors - массив цветов по умолчанию. В самих методах класса CCanvas он не используется. Но может быть полезен для других функций или методов классов-наследников CCanvas как палитра цветов.
  • Данные для взаимодействия с чартом. Представляют собой следующие данные: m_chart_id, m_objname, m_objtype, m_rcname и m_format - ID объекта чарта, имя объекта чарта, тип объекта чарта, имя графического ресурса и формат пикселя, соответственно. Предназначены для работы следующих методов: Destroy, Update, Resize. В частности поле m_format нужно для работы метода TextOut.


Рассмотрим методы:

  • Методы создания/прикрепления/удаления. В данной категории собраны разные методы, которые работают с объектом чарта. Это методы создания графического объекта: CreateBitmap и CreateBitmapLabel. Предназначены для создания объекта чарта и связанного с ним графического ресурса, который в свою очередь осуществляет вывод изображения в чарт. Методы прикрепления: Attach. Если в чарте есть графический объект OBJ_BITMAP_LABEL с прикрепленным графическим ресурсом или без него, то CCanvas будет работать с ним точно также, как и с созданным графическим объектом. Достаточно прикрепить его с помощью соответствующего метода Attach. Метод удаления представлен как Destroy. Он удаляет графический объект, освобождает буфер массива пикселей m_pixels, и удаляет графический ресурс, связанный с объектом чарта. Поэтому для корректной работы CCanvas в приложениях нужно всегда вызывать метод Destroy при завершении его работы, поскольку CCanvas не удаляет свой объект чарта и графический ресурс автоматически.
  • Методы загрузки изображений из файла. Здесь представлены следующие методы. Статический метод LoadBitmap, который способен загружать изображение из файла *.bmp в любой uint-массив, переданный ему по адресу в качестве параметра, сохраняя размеры полученного изображения в переменные width и height, которые также передаются в данный метод по адресам в качестве его параметров. Другой метод LoadFromFile загружает изображение из файла *.bmp в массив m_pixels, устанавливая размеры изображения m_width и m_height. Формат пикселя m_format при этом должен быть равен COLOR_FORMAT_ARGB_RAW.
  • Методы чтения свойств объекта чарта. Состоят из ChartObjectName, ResourceName, Width и Height и возвращают, имя объекта чарта, имя графического ресурса, ширину и высоту изображения, соответственно. Эти методы предоставляют пользователю чтение лишь некоторых данных для взаимодействия с чартом, таких как m_objname, m_rcname, а также данных изображения m_width и m_height.
  • Методы чтения/записи свойств шрифта выводимого текста. Рассмотрим сначала методы записи: FontNameSet, FontSizeSet, FontFlagsSet и FontAngleSet. Эти методы устанавливают название шрифта, размер шрифта, атрибуты шрифта и угол наклона выводимого текста, соответственно. Теперь рассмотрим методы чтения: FontSizeGet, FontFlagsGet и FontAngleGet, которые возвращают размер шрифта, атрибуты шрифта и угол наклона выводимого текста, соответственно. Также существуют методы получения/установки свойств шрифта, которые возвращают/устанавливают все свойства шрифта сразу. Метод для установки свойств: FontSet - устанавливает название шрифта, размер шрифта, атрибуты шрифта и угол наклона выводимого текста. Метод для получения свойств: FontGet - возвращает название шрифта, размер шрифта, атрибуты шрифта и угол наклона выводимого текста. 
  • Методы чтения/записи стиля рисования линий. Для чтения используется метод LineStyleGet. Для записи - LineStyleSet. Стиль линий нужен для работы методов рисования графических примитивов LineAA, PolylineAA, PolygonAA, TriangleAA, CircleAA, EllipseAA, LineWu, PolylineWu, PolygonWu, TriangleWu, CircleWu, EllipseWu, LineThickVertical, LineThickHorizontal, LineThick, PolylineThick, PolygonThick.
  • Методы рисования в массиве пикселей. Класс CCanvas имеет довольно много методов рисования графических примитивов с использованием различных алгоритмов, что даёт пользователю возможность создавать сложную графику с применением прогрессивных методов сглаживания, таких как антиалиасинг, Ву (Wu’s) алгоритм и кривые Безье. Рассмотрим эти методы. Простые примитивы без сглаживания: LineVertical, LineHorizontal, Line, PolylinePolygon, Rectangle, Triangle, Circle, Ellipse, Arc и Pie. Методы рисуют следующие примитивы: вертикальная линия, горизонтальная линия, произвольная линия, ломаная линия, полигон, прямоугольник, треугольник, окружность, эллипс, дуга и закрашенный сектор эллипса, соответственно. Закрашенные примитивы: FillRectangle, FillTriangle, FillPolygon, FillCircle, FillEllipse и Fill. Эти методы рисуют: прямоугольник, треугольник, полигон, круг, эллипс и закрашивание области, соответственно. Методы рисования примитивов со сглаживанием с помощью антиалиасинга (AA): PixelSetAA, LineAA, PolylineAA, PolygonAA, TriangleAA, CircleAA и EllipseAA. Производят, закрашивание пикселя и выводят такие примитивы как произвольная линия, ломаная линия, полигон, треугольник, окружность и эллипс, соответственно. Методы рисования примитивов с помощью алгоритма Ву (Wu’s): LineWu, PolylineWu, PolygonWu, TriangleWu, CircleWu и EllipseWu. Рисуют такие примитивы как произвольная линия, ломаная линия, полигон, треугольник, окружность и эллипс, соответственно. Методы рисования примитивов с предварительно отфильтрованным антиалиасингом и регулируемой толщиной линий: LineThickVertical, LineThickHorizontal, LineThick, PolylineThick и PolygonThick. Рисуют следующие примитивы: вертикальная линия, горизонтальная линия, произвольная линия, ломаная линия и полигон, соответственно. Методы рисования сглаженных примитивов с помощью методов Безье: PolylineSmooth и PolygonSmooth. Рисуют такие примитивы как сглаженная линия и сглаженный полигон, соответственно. Помимо вышеперечисленных методов к данной категории также можно отнести метод для вывода текста TextOut, поскольку он также меняет значения цветов в массиве пикселей, хотя в исходном коде класса CCanvas он относится к методам работы с текстом.
  • Методы передачи изображения для вывода в чарт. К данной категории относятся два метода. Метод Update, который передаёт массив пикселей m_pixels графическому ресурсу, связанному с объектом чарта, который выводит в него изображение. Напоминаю, массив пикселей m_pixels изменяется с помощью методов рисования в массиве пикселей, которые описаны выше. А также метод Resize, который изменяет размеры массива m_pixels (размеры изображения) и также передает его графическому ресурсу.
  • Сервисы. В качестве сервисов в CCanvas существуют два метода: GetDefaultColor, который возвращает предопределенные цвета, и TransparentLevelSet, который меняет прозрачность изображения, изменяя значения альфа-канала в массиве m_pixels.
  • Прочие настройки. Здесь всего один метод FilterFunction – метод установки фильтра для антиалиасинга, который устанавливает фильтр для всех методов рисования, имеющих в своём названии символы AA.

Класс CCanvas имеет поля и методы в области видимости private, которые мы не будем рассматривать в рамках данной статьи, поскольку они являются внутренними и не могут быть использованы в переопределенных методах классов наследников CCanvas. Но их можно изучить, открыв исходный код модуля Canvas.mqh в MetaEditor.


Последовательность действий при использовании CCanvas в MQL приложениях

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

  • Создать или прикрепить объект чарта (OBJ_BITMAP или OBJ_BITMAP_LABEL) либо прикрепиться к уже существующему OBJ_BITMAP_LABEL
  • Задать параметры шрифта и стили рисования примитивов
  • Произвести рисование в массиве m_pixels с помощью соответствующих методов
  • Обновить ресурс графического объекта (OBJ_BITMAP или OBJ_BITMAP_LABEL)

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

Создание объекта чарта и прикрепление к уже существующему объекту чарта

Для того, чтобы MQL приложение могло выводить графику в чарт, следует создать объект на основе класса CCanvas или на основе класса наследника CCanvas. После создания объекта CCanvas можно приступить к созданию объекта чарта OBJ_BITMAP или OBJ_BITMAP_LABEL. Либо прикрепить уже существующий OBJ_BITMAP_LABEL к созданному объекту CCanvas.

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

bool  CreateBitmap(const long chart_id, const int subwin, const string name, const datetime time, const double price, const int width, const int height, ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

bool  CreateBitmap(const string name, const datetime time, const double price, const int width, const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

bool  CreateBitmapLabel(const long chart_id, const int subwin, const string name, const int x, const int y, const int width, const int height, ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

bool  CreateBitmapLabel(const string name,const int x,const int y, const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

Метод CreateBitmap создает битмап (тип объекта OBJ_BITMAP), координаты которого задаются в виде времени и цены на графике торгового инструмента, и содержит следующие параметры:

  • chart_id – идентификатор чарта (0 – текущий чарт)
  • window – номер подокна чарта (0 – главное окно)
  • name – имя создаваемого графического объекта на чарте
  • time – координата времени графического объекта на чарте
  • price – координата цены графического объекта на чарте
  • width – ширина графического объекта на чарте
  • height – высота графического объекта на чарте

Другой вариант метода CreateBitmap - это перегруженный метод, который вызывает CreateBitmap с chart_id и window равными 0 (что соответствует текущему чарту и главному окну).

//+------------------------------------------------------------------+
//| Create object on chart with attached dynamic resource            |
//+------------------------------------------------------------------+
bool CCanvas::CreateBitmap(const string name,const datetime time,const double price,
                           const int width,const int height,ENUM_COLOR_FORMAT clrfmt)
  {
   return(CreateBitmap(0,0,name,time,price,width,height,clrfmt));
  }

Метод CreateBitmapLabel имеет всё те же параметры, что и CreateBitmap, за исключением time и price. Вместо них мы видим x и y.

Параметры:

  • chart_id – идентификатор чарта (0 – текущий чарт)
  • window – номер подокна чарта (0 – главное окно)
  • name – имя создаваемого графического объекта на чарте
  • x – координата по оси X графического объекта на чарте
  • y – координата по оси Y графического объекта на чарте
  • width – ширина изображения создаваемого графического объекта
  • height – высота изображения создаваемого графического объекта
  • clrfmt – формат цвета пикселей изображения создаваемого графического объекта

Другой вариант метода CreateBitmapLabel - это перегруженный метод, который вызывает CreateBitmapLabel с chart_id и window равными 0 (как в случае с CreateBitmap).

//+------------------------------------------------------------------+
//| Create object on chart with attached dynamic resource            |
//+------------------------------------------------------------------+
bool CCanvas::CreateBitmapLabel(const string name,const int x,const int y,
                                const int width,const int height,ENUM_COLOR_FORMAT clrfmt)
  {
   return(CreateBitmapLabel(0,0,name,x,y,width,height,clrfmt));
  }

Если на графике есть объект OBJ_BITMAP_LABEL, то его можно прикрепить к CCanvas с помощью метода Attach. В результате объект чарта будет взаимодействовать с объектом CCanvas точно также, как и с объектом чарта, который создан с помощью метода CreateBitmap или CreateBitmapLabel.

virtual bool  Attach(const long chart_id,const string objname,const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

virtual bool  Attach(const long chart_id,const string objname,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

Данный метод прикрепляет CCanvas к уже существующему объекту чарта OBJ_BITMAP_LABEL.

Параметры:

  • chart_id – идентификатор чарта (0 – текущий чарт)
  • objname – имя прикрепляемого графического объекта
  • width – ширина изображения прикрепляемого графического объекта
  • height – высота изображения прикрепляемого графического объекта
  • clrfmt – формат цвета пикселей изображения прикрепляемого графического объекта

Первый вариант метода Attach предполагает, что у объекта чарта нет графического ресурса, и самостоятельно создает его исходя из параметров objname, width, height и clrfmt, заполняя все данные, которые необходимы для дальнейшей работы с прикрепленным графическим объектом.

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

Методы установки и получения параметров шрифта, а также методы установки и получения стиля рисования линий

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

Простые методы установки свойств шрифта:

bool FontNameSet(string name);  // Устанавливает название шрифта

bool FontSizeSet(int size);     // Устанавливает размер шрифта

bool FontFlagsSet(uint flags);  // Устанавливает атрибуты шрифта

bool FontAngleSet(uint angle);  // Устанавливает угол наклона шрифта

Простые методы чтения свойств шрифта:

string FontNameGet(void) const;   // Возвращает название шрифта

int    FontSizeGet(void) const;   // Возвращает размер шрифта

uint   FontFlagsGet(void) const;  // Возвращает атрибуты шрифта

uint   FontAngleGet(void) const;  // Возвращает угол наклона шрифта

Метод установки всех свойств шрифта

bool FontSet(const string name,const int size,const uint flags=0,const uint angle=0); // Устанавливает свойства шрифта

Параметры:

  • name - название шрифта
  • size - размер шрифта
  • flags - атрибуты шрифта
  • angle - угол наклона шрифта

Как можно видеть из листинга, данный метод устанавливает название шрифта, размер шрифта, атрибуты шрифта и угол наклона текста согласно значениям переменных name, size, flags и angle, соответственно, которые передаются ему в качестве параметров.

Метод получения всех свойств шрифта

void FontGet(string &name,int &size,uint &flags,uint &angle); // Получает свойства шрифта

Параметры:

  • name - название шрифта
  • size - размер шрифта
  • flags - атрибуты шрифта
  • angle - угол наклона шрифта

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

Методы чтения и записи стиля линий графических построений:

uint LineStyleGet(void) const;       // Возвращает установленный стиль рисования линий

void LineStyleSet(const uint style); // Устанавливает стиль рисования линий

    Параметры:

    • style - стиль рисования линий

    Методы рисования, а также, метод вывода текста 

    Начнем с рассмотрения метода вывода текста, поскольку он один. В то время как методов рисования графических примитивов в CCanvas достаточно много.

    Вывод текста

    void TextOut(int x,int y,string text,const uint clr,uint alignment=0); // Выводит текст в массив m_pixels

    Параметры:

    • x - координата по оси X выводимого текста
    • y - координата по оси Y выводимого текста
    • text - выводимый текст
    • clr - цвет выводимого текста
    • alignment - способ привязки выводимого текста

    Как видно из листинга, метод выводит текст text, согласно координатам x, y, с заданным цветом clr и способом привязки текста alignment.

    Изменение пикселей

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

    uint PixelGet(const int x,const int y) const;          // Возвращает значение цвета пикселя согласно координатам x, y
    
    void PixelSet(const int x,const int y,const uint clr); // Меняет значение цвета пикселя согласно координатам x, y
    
    

    Параметры:

    • x - координата по оси X пикселя
    • y - координата по оси Y пикселя
    • clr - цвет пикселя

    Рисование графических примитивов

    Поскольку методов для рисования примитивов в CCanvas довольно много, то в рамках данной статьи мы разберем только основные из них. Обо всех остальных методах класса CCanvas можно подробно узнать в документации.

    Вертикальная линия

    void LineVertical(int x,int y1,int y2,const uint clr); // Рисует вертикальную линию согласно заданным координатам и цвету

    Параметры:

    • x - координата линии по оси X
    • y1 - координата первой точки по оси Y
    • y2 - координата второй точки по оси Y
    • clr - цвет линии

    Горизонтальная линия

    void LineHorizontal(int x1,int x2,int y,const uint clr); // Рисует горизонтальную линию согласно заданным координатам и цвету

    Параметры:

    • x1 - координата первой точки по оси X
    • x2 - координата второй точки по оси X
    • y - координата линии по оси Y
    • clr - цвет линии

    Произвольная линия

    void Line(int x1,int y1,int x2,int y2,const uint clr); // Рисует произвольную линию согласно заданным координатам и цвету

    Параметры:

    • x1 - координата первой точки по оси X
    • y1 - координата первой точки по оси
    • x2 - координата второй точки по оси
    • y2 - координата второй точки по оси Y
    • clr - цвет линии

    Ломаная линия

    void Polyline(int &x[],int &y[],const uint clr); // Рисует ломаную линию согласно заданным координатам и цвету

    Параметры:

    • x - массив координат точек по оси X
    • y - массив  координат точек по оси Y
    • clr - цвет линии

    Полигон

    void Polygon(int &x[],int &y[],const uint clr); // Рисует полигон согласно заданным координатам и цвету

    Параметры:

    • x - массив координат точек по оси X
    • y - массив  координат точек по оси Y
    • clr - цвет полигона

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

    void Rectangle(int x1,int y1,int x2,int y2,const uint clr); // Рисует прямоугольник согласно заданным координата и цвету

    Параметры:

    • x1 - координата первой точки по оси X
    • y1 - координата первой точки по оси Y
    • x2 - координата второй точки по оси X
    • y2 - координата второй точки по оси Y
    • clr - цвет прямоугольника 

    Треугольник

    void Triangle(int x1,int y1,int x2,int y2,int x3,int y3,const uint clr); // Рисует треугольник согласно заданным координатами и цвету

    Параметры:

    • x1 - координата первой точки по оси X
    • y1 - координата первой точки по оси Y
    • x2 - координата второй точки по оси X
    • y2 - координата второй точки по оси Y
    • x3 - координата третьей точки по оси X
    • y3 - координата третьей точки по оси Y
    • clr - цвет треуголниька

    Окружность

    void Circle(int x,int y,int r,const uint clr); // Рисует окружность согласно, заданным координатам, радиусу и цвету

    Параметры:

    • x - координата по оси X
    • y - координата по оси Y
    • r - радиус окружности
    • clr - цвет окружности

    Эллипс

    void Ellipse(int x1,int y1,int x2,int y2,const uint clr); // Рисует эллипс согласно заданным координатам и цвету

    Параметры:

    • x1 - координата первой точки по оси X
    • y1 - координата первой точки по оси Y
    • x2 - координата второй точки по оси X
    • y2 - координата второй точки по оси Y
    • clr - цвет эллипса

    Описания остальных методов, таких как Arc и Pie, являются слишком обширными в рамках данной статьи. Поэтому рекомендую смотреть описания этих методов в документации по вышеприведенным ссылкам.

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

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

    //--- Методы для вывода примитивов со сглаживанием с помощью антиалиасинга
        
    void PixelSetAA(const double x,const double y,const uint clr);
        
    void LineAA(const int x1,const int y1,const int x2,const int y2,const uint clr,const uint style=UINT_MAX);
        
    void PolylineAA(int &x[],int &y[],const uint clr,const uint style=UINT_MAX);
        
    void PolygonAA(int &x[],int &y[],const uint clr,const uint style=UINT_MAX);
        
    void TriangleAA(const int x1,const int y1,const int x2,const int y2,const int x3,const int y3,const uint clr,const uint style=UINT_MAX);
        
    void CircleAA(const int x,const int y,const double r,const uint clr,const uint style=UINT_MAX);
        
    void EllipseAA(const double x1,const double y1,const double x2,const double y2,const uint clr,const uint style=UINT_MAX);
        
    //--- Методы для вывода примитивов со сглаживанием с помощью Ву (Wu's) алгоритма
        
    void LineWu(int x1,int y1,int x2,int y2,const uint clr,const uint style=UINT_MAX);
        
    void PolylineWu(const int &x[],const int &y[],const uint clr,const uint style=UINT_MAX);
        
    void PolygonWu(const int &x[],const int &y[],const uint clr,const uint style=UINT_MAX);
        
    void TriangleWu(const int x1,const int y1,const int x2,const int y2,const int x3,const int y3,const uint clr,const uint style=UINT_MAX);
        
    void CircleWu(const int x,const int y,const double r,const uint clr,const uint style=UINT_MAX);
        
    void EllipseWu(const int x1,const int y1,const int x2,const int y2,const uint clr,const uint style=UINT_MAX);

    Все эти методы являются схожими с вышерассмотренными простыми методами но имеют один дополнительный параметр style), который можно выбрать из перечисления ENUM_LINE_STYLE). В листинге он выделен желтым. Если параметр style не задан (равен значению UINT_MAX), то вызываемый метод использует значение m_style, которое устанавливается с помощью метода LineStyleSet и которое можно получить с помощью метода LineStyleGet.

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

    //--- Методы для рисования примитивов с предварительно установленным фильтром антиалиасинга
    
    void LineThickVertical(const int x,const int y1,const int y2,const uint clr,const int size,const uint style,ENUM_LINE_END end_style);
    
    void LineThickHorizontal(const int x1,const int x2,const int y,const uint clr,const int size,const uint style,ENUM_LINE_END end_style);
    
    void LineThick(const int x1,const int y1,const int x2,const int y2,const uint clr,const int size,const uint style,ENUM_LINE_END end_style);
    
    void PolylineThick(const int &x[],const int &y[],const uint clr,const int size,const uint style,ENUM_LINE_END end_style);
    
    void PolygonThick(const int &x[],const int &y[],const uint clr,const int size,const uint style,ENUM_LINE_END end_style);

    В этих методах, как и в методах, рассмотренных выше, существует возможность установки стиля линий style. Также добавлены новые параметры, такие как size - установка толщины линий, а также end_style - стиль завершения линий, который можно выбрать из перечисления ENUM_LINE_END.

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

    //--- Методы для рисования сглаженной ломаной линии и сглаженного полигона
    
    void PolylineSmooth(const int &x[],const int &y[],const uint clr,const int size,
                        ENUM_LINE_STYLE style=STYLE_SOLID,ENUM_LINE_END end_style=LINE_END_ROUND,
                        double tension=0.5,double step=10);
    
    void PolygonSmooth(int &x[],int &y[],const uint clr,const int size,
                       ENUM_LINE_STYLE style=STYLE_SOLID,ENUM_LINE_END end_style=LINE_END_ROUND,
                       double tension=0.5,double step=10);

    Здесь, помимо уже знакомых нам массивов координат точек x и y примитива, а также цвета clr и толщины линий size, стиля линий style и стиля завершения линий end_style, существуют два дополнительных параметра tension - значение параметра сглаживания и step - шаг аппроксимации.

    Помимо рассмотренных выше методов рисования примитивов, состоящих из линий, в CCanvas есть методы рисования закрашенных примитивов.

    //--- Методы рисования закрашенных примитивов
    
    void FillRectangle(int x1,int y1,int x2,int y2,const uint clr);
    
    void FillTriangle(int x1,int y1,int x2,int y2,int x3,int y3,const uint clr);
    
    void FillPolygon(int &x[],int &y[],const uint clr);
    
    void FillCircle(int x,int y,int r,const uint clr);
    
    void FillEllipse(int x1,int y1,int x2,int y2,const uint clr);
    
    void Fill(int x,int y,const uint clr);
    
    void Fill(int x,int y,const uint clr,const uint threshould);

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


    Формат пикселя и компоненты цвета

    Предлагаю вернуться к рассмотренным выше методам и обратить внимание на формат пикселя в методах CreateBitmap и CreateBitmapLabel, за который отвечает параметр clrfmt. Данный параметр задаёт формат пикселя создаваемому графическому ресурсу, который в последующем связывается с объектом чарта, выводящим изображение в него. От того, какой формат пикселя мы зададим графическому ресурсу во время создания Canvas с помощью методов  CreateBitmap или CreateBitmapLabel, будет зависеть способ обработки изображения терминалом во время его вывода в чарт. Для того, чтобы понять, какие способы обработки изображения существуют, обратимся к перечислению ENUM_COLOR_FORMAT. Здесь можно выбрать одно из трёх значений констант.

    ENUM_COLOR_FORMAT

    • COLOR_FORMAT_XRGB_NOALPHA - формат XRGB (альфа-канал игнорируется)
    • COLOR_FORMAT_ARGB_RAW -  "сырой" формат ARGB (компоненты цвета не обрабатываются терминалом)
    • COLOR_FORMAT_ARGB_NORMALIZE - формат ARGB (компоненты цвета обрабатываются терминалом)

    Теперь рассмотрим порядок представления компонентов цвета в данных форматах.

    Формат пикселя и компоненты цвета

    На представленной схеме мы видим расположение компонентов цвета в байтах ячеек массива пикселей m_pixels, где R - канал красного, G - канал зеленого, B - канал синего, A - альфа-канал, X - неиспользуемый байт. Тип данных пикселя uint, 4 байта. По одному байту на канал. Один байт может хранить числа от 0 до 255. Изменяя значения байт мы устанавливаем цвет пикселя. Так, например, если записать в канал R значение 255, а каналам G и B записать значения 0, то мы получим красный цвет. Если записать значение 255 в канал G, а остальным задавать значения 0, то получим зеленый цвет. А если в канал B записать значение 255, а в остальные записать значения 0, то мы получим синий цвет. Устанавливая значения каналов RGB в различных соотношениях, мы можем получить абсолютно любые цвета. Задавая значение альфа-канала от 0 (абсолютно невидимый, прозрачный) до 255 (абсолютно видимый, непрозрачный), мы можем регулировать непрозрачность пикселя, создавая эффект наложения пикселей изображения на изображение, которое уже выведено в чарт. Сделать это корректно можно в формате COLOR_FORMAT_ARGB_NORMALIZE. А сейчас перейдем непосредственно к цветам и рассмотрим цветовую палитру.

    Цветовая палитра

    Здесь можно наглядно увидеть, как изменение соотношения уровней каждого канала влияет на итоговый цвет. Цвета представлены в формате RGB и HEX. Если формат RGB мы с вами уже рассматривали, то HEX нуждается в пояснениях, особенно для начинающих программистов, которые хотят освоить CCanvas. Рассмотрим шестнадцатеричную систему счисления, которая содержит числа от 0 до 15 и сравним её с десятеричной системой счисления, где числа идут от 0 до 9. Для этих чисел в десятеричной системе есть символы: 0, 1, 2, 3, 4, 5, 6, 7, 8 и 9. Очевидно, у многих начинающих программистов возникнет вопрос, как для шестнадцатеричной системы счисления записать числа, которые больше 9. Здесь на помощь нам приходят символы латинского алфавита от A до F. Таким образом, для записи значений в шестнадцатеричной системе счисления мы получаем следующий набор символов: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Теперь можно легко понять, как будет выглядеть байт, в который записывается значение канала, в шестнадцатеричной системе счисления или в HEX-формате. В C-подобных языках программирования HEX записывается как 0x[значение]. Таким образом, диапазон одного байта будет выглядеть как значения от 0x00 до 0xFF.


    Примеры создания графики с помощью CCanvas

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

    Простое приложение с использованием CCanvas

    Создадим скрипт и назовем его Erase.mq5.

    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);
        canvas.Erase(ColorToARGB(clrWhite, 255));
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }

    В первой строке мы видим подключение модуля Canvas.mqh. Теперь можно создать объект, экземпляр класса CCanvas, для дальнейшей работы, что и сделано в примере.

    CCanvas  canvas;

    Далее, создаём сам Canvas с параметром clrfmt: COLOR_FORMAT_ARGB_NORMALIZE.

     canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);

    И заполняем его с помощью метода Erase.

     canvas.Erase(ColorToARGB(clrWhite, 255));

    После создания Canvas вызываем метод Update.

    canvas.Update(true);

    С параметром redraw: true. Данный параметр можно не устанавливать, поскольку он и так задан по умолчанию, но предлагаю сделать это для наглядности. Далее ждём 6 секунд, чтобы увидеть результат работы приложения в чарте.

    Sleep(6000); 

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

    canvas.Destroy();

    Далее скрипт завершает свою работу. Результат работы  можно увидеть на изображении ниже.

    Erase

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

    Формат пикселя и способы наложения

    Возьмем рассмотренный нами пример Erase.mq5 и попробуем задать цвет напрямую, без функции ColorToARGB. Скопируем весь код примера и создадим на основе него скрипт под названием ARGB_NORMALIZE.mq5. Теперь зададим цвет, выбрав один из цветов в рассмотренной нами цветовой палитре. Например, clrPaleGreen. Возьмём его значение в формате HEX и слева добавим значение альфа-канала 0xFF.

    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);
        canvas.Erase(0xFF98FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }

    В листинге компоненты RGB выделены зеленым цветом, компонент A выделен серым цветом. Запустим скрипт и посмотрим, что получилось.

    Изменение цвета в формате COLOR_FORMAT_ARGB_NORMALIZE

    Мы видим, как изменился цвет изображения. Теперь попробуем изменить прозрачность. Установим значение альфа-канала в значение 0xCC.

    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);
        canvas.Erase(0xCC98FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }

    И посмотрим на результат.

    Изменение непрозрачности в формате COLOR_FORMAT_ARGB_NORMALIZE

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

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

    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_RAW);
        canvas.Erase(0xFF98FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }

    Запустим пример и посмотрим на получившиийся результат.

    Изображение непрозрачности в формате COLOR_FORMAT_ARGB_RAW

    Мы видим, что результат ничем не отличается от примера с COLOR_FORMAT_ARGB_NORMALIZE.

    Установим значение альфа-канала в 0xFB и изменим цвет фона чарта на белый.

    void OnStart()
     {
        ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite);
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_RAW);
        canvas.Erase(0xFB98FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }

    В результате мы видим лишь небольшое изменение и никакой разницы с форматом пикселя COLOR_FORMAT_ARGB_NORMALIZE.

    Изменение непрозрачности в формате COLOR_FORMAT_ARGB_RAW

    Но стоит уменьшить значение альфа-канала всего на единицу,

    canvas.Erase(0xFA98FB98);

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

    Изменение изображения при небольшом изменении непрозрачности в формате COLOR_FORMAT_ARGB_RAW

    Теперь проверим, как ведет себя изображение при полном изменении прозрачности. Для этого создадим новый пример и назовём его  ARGB_RAW-2.mq5. Скопируем туда код из предыдущего примера и внесем в него небольшие изменения, так чтобы значение альфа-канала могло меняться от 255 до 0 с заданным интервалом.

    void OnStart()
     {
      ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite);
      CCanvas canvas;
      canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_RAW);
      canvas.Erase(0xFF98FB98);
      for(int a = 255; a >= 0; a--)
       {
        canvas.TransparentLevelSet((uchar)a);
        canvas.Update(true);
        Sleep(100);
       }
      canvas.Destroy();
     }

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

    canvas.TransparentLevelSet((uchar)a);

    Затем вызывается уже знакомый нам метод Update.

    canvas.Update(true);

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

    Sleep(100);

    Затем, приложение удаляет Canvas

    canvas.Destroy();

    и завершает свою работу. Результат можно увидеть на GIF-анимации.

    Полное изменение непрозрачности в формате COLOR_FORMAT_ARGB_RAW

    Из рассмотренного примера, а также из всех примеров с использованием формата COLOR_FORMAT_ARGB_RAW мы увидели, что изображение со значением альфа-канала 255 выводится корректно. Но стоит уменьшить это значение, как изображение начинает выводиться с искажениями, поскольку в данном формате нет нормализации значений каналов RGB, которые могут переполняться и таким образом создавать  артефакты и искажения, поскольку переполненные значения выше 255 просто отсекаются. Зато изображение с использованием данного формата выводится быстрее, нежели с использованием формата COLOR_FORMAT_ARGB_NORMALIZE.

    Вернемся к нашему примеру с использованием формата пикселя COLOR_FORMAT_ARGB_RAW ARGB_RAW.mq5. Создадим скрипт под названием XRGB_NOALPHA.mq5 и скопируем код из ARGB_RAW.mq5 в него, установив формат пикселя в COLOR_FORMAT_XRGB_NOALPHA, а также в методе Erase установим в ноль значение альфа-канала. 

    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_XRGB_NOALPHA);
        canvas.Erase(0x0098FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }

    Запустим скрипт и посмотрим результат.

    Наложение цвета в формате COLOR_FORMAT_XRGB_NOALPHA

    Мы видим, что результат ничем не отличается от примеров с форматами COLOR_FORMAT_ARGB_NORMALIZE и COLOR_FORMAT_ARGB_RAW, с максимальным значением альфа-канала (0xFF) в методе Erase. Таким образом, мы видим, что в данном формате значение альфа-канала полностью игнорируется и изображение просто накладывается поверх изображения чарта. 

    Вывод текста

    Мы знаем, что в CCanvas существует метод для вывода текста TextOut. Попробуем вывести текст. Создадим скрипт и назовем его TextOut.mq5. Возьмем в качестве основы пример Erase.mq5, который мы рассмотрели ранее, скопировав его код. Добавим метод для установки параметров шрифта FontSet и установим нужные значения, название шрифта "Calibri" и размер шрифта -210.

    Чтобы задать размер шрифта в пикселях, size в методах FontSet и FontSizeSet, следует умножить его на -10. Таким образом, для размера шрифта в 21 пиксель size будет равно -210.

    Остальные параметры оставим по умолчанию. Далее, добавим метод TextOut с параметром text: "Text". Все остальные строки, которые нужны для работы данного примера остались из скопированного кода.

    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);
        canvas.Erase(ColorToARGB(clrWhite, 255));
        canvas.FontSet("Calibri", -210);
        canvas.TextOut(15, 15, "Text", ColorToARGB(clrLightSlateGray, 255));
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }

    Посмотрим на результат работы скрипта.

    Вывод текста методом TextOut

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

    Изменим наш пример таким образом, чтобы изображение появлялось ровно в центре чарта и напоминало объект Label (OBJ_LABEL). Создадим новый пример на основе данного скрипта и назовем его TextOut-2.mq5.

    void OnStart()
     {
      string text = "Text";
      int width, height;
      const int textXDist = 10, textYDist = 5;
      int canvasX, canvasY;
      CCanvas canvas;
      
      canvas.FontSet("Calibri", -210);
      canvas.TextSize(text, width, height);
      
      canvasX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) / 2 - width / 2;
      canvasY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0) / 2 - height / 2;
      
      canvas.CreateBitmapLabel("canvas", canvasX, canvasY,
                               width + textXDist * 2, height + textYDist * 2,
                               COLOR_FORMAT_ARGB_NORMALIZE);
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, 255));
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }

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

    string text = "Text";

    Объявлены переменные width и height для сохранения размеров выводимого текста.

    int width, height;

    Объявлены две константы textXDist и textYDist, в которые сохранены отступы выводимого теста от правого и от верхнего края изображения, соответственно.

    const int textXDist = 10, textYDist = 5;

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

    int canvasX, canvasY;

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

    canvas.FontSet("Calibri", -210);

    Затем с помощью метода TextSize определяются размеры выводимого текста на экране (чтобы понять, каких размеров должно быть изображение), которые сохраняются в переменные width и height.

    canvas.TextSize(text, width, height);

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

    canvasX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) / 2 - width / 2;
    canvasY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0) / 2 - height / 2;

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

    canvas.CreateBitmapLabel("canvas", canvasX, canvasY,
                               width + textXDist * 2, height + textYDist * 2,
                               COLOR_FORMAT_ARGB_NORMALIZE);

    Затем изображение заполняется цветом с помощью метода Erase.

    canvas.Erase(ColorToARGB(clrWhite, 255));

    Затем выводится текст (text) с помощью метода TextOut с указанием ранее установленных координат textXDist и textYDist.

    canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, 255));

    Далее идут знакомые уже нам строки, которые не нуждаются в разъяснениях. Запустим скрипт и смотрим получившийся результат.

    Пример текстовой метки

    На изображении мы видим объект, похожий на текстовую метку. Но ей не хватает прозрачного фона. Его можно задать и в нашем текущем формате пикселя COLOR_FORMAT_ARGB_NORMALIZE. Но мы поступим по-другому. Зададим нашему Canvas быстрый формат пикселя COLOR_FORMAT_ARGB_RAW, поскольку в данном случае смешивание цветов нам не нужно, а цвета со значением альфа-канала 0 и 255  в данном формате будут выводиться без искажений. При том, что цвет со значением альфа-канала 0  вообще никак не будет влиять на финальное изображение. Скопируем скрипт LabelExample.mq5 на основе предыдущего примера.

    void OnStart()
     {
      string text = "Text";
      int width, height;
      const int textXDist = 10, textYDist = 5;
      int canvasX, canvasY;
      CCanvas canvas;
      
      canvas.FontSet("Calibri", -210);
      canvas.TextSize(text, width, height);
      
      canvasX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) / 2 - width / 2;
      canvasY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0) / 2 - height / 2;
      
      canvas.CreateBitmapLabel("canvas", canvasX, canvasY,
                               width + textXDist * 2, height + textYDist * 2,
                               COLOR_FORMAT_ARGB_RAW);
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.FontSet("Calibri", -210);
      canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, 255));
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }

    Значение альфа-канала мы не меняли для того, чтобы убедиться в вышесказанном (что значение альфа-канала 255 будет полностью перекрашивать пиксели чарта).

    Проверка формата COLOR_FORMAT_ARGB_RAW

    Теперь установим значение альфа-канала в 0.

    void OnStart()
     {
      string text = "Text";
      int width, height;
      const int textXDist = 10, textYDist = 5;
      int canvasX, canvasY;
      CCanvas canvas;
      
      canvas.FontSet("Calibri", -210);
      canvas.TextSize(text, width, height);
      
      canvasX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) / 2 - width / 2;
      canvasY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0) / 2 - height / 2;
      
      canvas.CreateBitmapLabel("canvas", canvasX, canvasY,
                               width + textXDist * 2, height + textYDist * 2,
                               COLOR_FORMAT_ARGB_RAW);
      canvas.Erase(ColorToARGB(clrWhite, 0));
      canvas.FontSet("Calibri", -210);
      canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, 255));
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }

    Запустим скрипт и посмотрим, что у нас получилось.

    Аналог Label

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

    Рисование простых примитивов без сглаживания

    Для того чтобы понять, как рисовать примитивы в CCanvas, рассмотрим пример DrawPrimitives.mq5.

    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {14, 12, 13, 11, 12, 10};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {9, 7, 5, 5, 7, 9};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.LineHorizontal(point, w - point, h - point, color_);
      canvas.LineVertical(point, point, h - point, color_);
      canvas.Line(point * 2, point * 13, point * 8, point * 9, color_);
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      canvas.Polyline(plX, plY, color_);
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
      canvas.Polygon(pgX, pgY, color_);
      canvas.Rectangle(point * 2, point * 5, point * 7, point, color_);
      canvas.Triangle(point * 2, point * 11, point * 2, point * 6, point * 7, point * 6, color_);
      canvas.Circle(point * 10, point * 3, point * 2, color_);
      canvas.Ellipse(point * 8, point * 9, point * 12, point * 6, color_);
      canvas.Arc(point * 15, point * 2, point * 2, point, 45.0 * M_PI / 180, 180.0 * M_PI / 180, color_);
      canvas.Pie(point * 16, point * 3, point * 2, point, 180.0 * M_PI / 180, 402.5 * M_PI / 180, color_, fillColor);
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }

    Все примитивы удобно рисовать по клеткам, точно таким же образом удобно задавать их координаты и остальные параметры. Поэтому я решил упростить задачу, поделив изображение CCanvas на части, равные по ширине и высоте, для того, чтобы можно было задавать координаты в клетках, а затем переводить их в пиксели, умножая координаты в клетках на размер одной клетки. Для этого я взял размеры чарта в качестве размеров изображения CCanvas и поделил наименьший размер на 15.

      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {14, 12, 13, 11, 12, 10};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {9, 7, 5, 5, 7, 9};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;

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

    int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {14, 12, 13, 11, 12, 10};
    int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {9, 7, 5, 5, 7, 9};

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

      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      canvas.Polyline(plX, plY, color_);
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
      canvas.Polygon(pgX, pgY, color_);

    Код вызова методов рисования примитивов выделен желтым. Остальные примитивы выводятся следующим образом.

      canvas.Polygon(pgX, pgY, color_);
      canvas.Rectangle(point * 2, point * 5, point * 7, point, color_);
      canvas.Triangle(point * 2, point * 11, point * 2, point * 6, point * 7, point * 6, color_);
      canvas.Circle(point * 10, point * 3, point * 2, color_);
      canvas.Ellipse(point * 8, point * 9, point * 12, point * 6, color_);
      canvas.Arc(point * 15, point * 2, point * 2, point, 45.0 * M_PI / 180, 180.0 * M_PI / 180, color_);
      canvas.Pie(point * 16, point * 3, point * 2, point, 180.0 * M_PI / 180, 402.5 * M_PI / 180, color_, fillColor);

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

    Простые примитивы

    Мы видим примитивы с простым стилем рисования линий (STYLE_SOLID), который нельзя менять.

    Рисование примитивов со сглаживанием и изменяемым стилем линий

    Для того, чтобы стиль линий можно было менять, воспользуемся методами с алгоритмом фильтрации с помощью антиалиасинга (AA). Создадим скрипт под названием DrawPrimitivesAA.mq5 и скопируем туда код из предыдущего примера. Все методы, которые существуют в классе CCanvas с приставкой AA (LineAA, PolylineAA, PolygonAA, TriangleAA, CircleAA и EllipseAA) оставим, приведя их в полное соответствие. Остальные методы удалим. Поскольку примитивов стало меньше, по сравнению с предыдущим примером, изменим их расположение.

    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {7, 5, 3, 3, 5, 7};
      ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.LineStyleSet(lineStyle);
      canvas.LineAA(point * 2, point * 12, point * 8, point * 8, color_);
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      canvas.PolylineAA(plX, plY, color_);
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
      canvas.PolygonAA(pgX, pgY, color_);
      canvas.TriangleAA(point * 2, point * 9, point * 2, point * 4, point * 7, point * 4, color_);
      canvas.CircleAA(point * 16, point * 11, point * 2, color_);
      canvas.EllipseAA(point * 8, point * 4, point * 12, point * 7, color_);
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }

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

    Примитивы с антиалиасингом

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

    Мы увидели результат работы методов рисования примитивов с использованием антиалиасинга, но стиль линий остался по-прежнему STYLE_SOLID. Исправим это, создав скрипт под названием  DrawPrimitivesAA-2.mq5, вставив туда код предыдущего примера. Пропишем параметр sleep в качестве input-параметра, который будет задавать задержку после смены стиля линий и вывода примитивов.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;

    Все методы рисования вынесем в макрос.

    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                               \
      canvas.LineAA(point * 2, point * 12, point * 8, point * 8, color_);                          \
      canvas.PolylineAA(plX, plY, color_);                                                         \
      canvas.PolygonAA(pgX, pgY, color_);                                                          \
      canvas.TriangleAA(point * 2, point * 9, point * 2, point * 4, point * 7, point * 4, color_); \
      canvas.CircleAA(point * 16, point * 11, point * 2, color_);                                  \
      canvas.EllipseAA(point * 8, point * 4, point * 12, point * 7, color_);                       \
      canvas.Update(true);                                                                         \
      Sleep(sleep);

    Затем поочередно будем изменять стиль рисования линий с помощью метода LineStyleSet и выводить примитивы. Рассмотрим получившийся пример.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                               \
      canvas.LineAA(point * 2, point * 12, point * 8, point * 8, color_);                          \
      canvas.PolylineAA(plX, plY, color_);                                                         \
      canvas.PolygonAA(pgX, pgY, color_);                                                          \
      canvas.TriangleAA(point * 2, point * 9, point * 2, point * 4, point * 7, point * 4, color_); \
      canvas.CircleAA(point * 16, point * 11, point * 2, color_);                                  \
      canvas.EllipseAA(point * 8, point * 4, point * 12, point * 7, color_);                       \
      canvas.Update(true);                                                                         \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {7, 5, 3, 3, 5, 7};
      //ENUM_LINE_STYLE lineStyle;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
      
      canvas.LineStyleSet(STYLE_SOLID);
      drawPrimitives
      canvas.LineStyleSet(STYLE_DOT);
      drawPrimitives
      canvas.LineStyleSet(STYLE_DASH);
      drawPrimitives
      canvas.LineStyleSet(STYLE_DASHDOTDOT);
      drawPrimitives
      
      Sleep(6000);
      canvas.Destroy();
     }

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

    Смена стиля рисования линий (AA)

    На данной GIF-анимации мы видим, как меняется стиль линий примитивов с заданным интервалом sleep.

    Теперь предлагаю создать пример рисования примитивов с использованием алгоритма Wu. Создадим скрипт DrawPrimitivesWu.mq5 на основе предыдущего примера. Сочетание символов AA заменим на Wu и закомментируем строки, как показано в листинге.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                               \
      canvas.LineWu(point * 2, point * 12, point * 8, point * 8, color_);                          \
      canvas.PolylineWu(plX, plY, color_);                                                         \
      canvas.PolygonWu(pgX, pgY, color_);                                                          \
      canvas.TriangleWu(point * 2, point * 9, point * 2, point * 4, point * 7, point * 4, color_); \
      canvas.CircleWu(point * 16, point * 11, point * 2, color_);                                  \
      canvas.EllipseWu(point * 8, point * 4, point * 12, point * 7, color_);                       \
      canvas.Update(true);                                                                         \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {7, 5, 3, 3, 5, 7};
      //ENUM_LINE_STYLE lineStyle;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
    
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.LineStyleSet(STYLE_SOLID);
      drawPrimitives
      //canvas.LineStyleSet(STYLE_DOT);
      //drawPrimitives
      //canvas.LineStyleSet(STYLE_DASH);
      //drawPrimitives
      //canvas.LineStyleSet(STYLE_DASHDOTDOT);
      //drawPrimitives
      
      Sleep(6000);
      canvas.Destroy();
     }

    Все добавленные методы выделены желтым. Запустим пример и посмотрим результат.

    Примитивы со сглаживанием Wu

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

    Смена стиля рисования линий (Wu's)

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

    Рисование примитивов с изменяемой толщиной линий

    Создадим скрипт на основе предыдущего примера и назовём его DrawPrimitivesThick.mq5. Заменим существующие методы рисования примитивов методами с приставкой "Thick" и удалим те методы у которых нет аналогов, как показано в листинге. И как в предыдущем примере, закомментируем ненужные строки.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                                      \
      canvas.LineThickVertical(point, point, h - point, color_, size, lineStyle, endStyle);               \
      canvas.LineThickHorizontal(point, w - point, h - point, color_, size, lineStyle, endStyle);         \
      canvas.LineThick(point * 2, point * 12, point * 8, point * 8, color_, size, lineStyle, endStyle);   \
      canvas.PolylineThick(plX, plY, color_, size, lineStyle, endStyle);                                  \
      canvas.PolygonThick(pgX, pgY, color_, size, lineStyle, endStyle);                                   \
      canvas.Update(true);                                                                                \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {17, 18, 17, 15, 14, 15, 17}, pgY[] = {7, 5, 3, 3, 5, 7, 7};
      ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
      int size = 3;
      ENUM_LINE_END endStyle = LINE_END_ROUND;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.LineStyleSet(STYLE_SOLID);
      drawPrimitives
      //canvas.LineStyleSet(STYLE_DOT);
      //drawPrimitives
      //canvas.LineStyleSet(STYLE_DASH);
      //drawPrimitives
      //canvas.LineStyleSet(STYLE_DASHDOTDOT);
      drawPrimitives
      
      Sleep(6000);
      canvas.Destroy();
     }

    В листинге добавленные методы выделены желтым. Как уже заметили внимательные читатели, у рассматриваемых методов есть два дополнительных параметра: size - толщина линий и end_style - стиль завершения линии. Запустим скрипт и посмотрим на получившийся результат.

    Вывод примитивов (Thick)

    На изображении мы видим примитивы с толстыми линиями, толщину которых можно изменять, как уже было сказано выше. Создадим новый скрипт на основе предыдущего и назовём его DrawPrimitivesThick-2.mq5. С его помощью мы будем смотреть все возможные сочетания толщины линии, стилей линий, и стилей завершения линии. Для этого раскомментируем строки, которые были закомментированы и добавим их в макрос method1, где будем менять стиль завершения линий и вызывать после каждого изменения макрос method0. В макросе  method0 мы будем вызывать методы рисования примитивов. В макросе drawPrimitives будем менять стиль линий и вызывать method1 после каждого  изменения стиля линий. Макрос drawPrimitives будем вызывать в цикле, где будем изменять толщину линий в заданном диапазоне.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    input int beginSize = 1;
    input int endSize = 4;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives lineStyle = STYLE_SOLID; \
      method1                                       \
      lineStyle = STYLE_DOT;                        \
      method1                                       \
      lineStyle = STYLE_DASH;                       \
      method1                                       \
      lineStyle = STYLE_DASHDOTDOT;                 \
      method1 
      
    #define method1 endStyle = LINE_END_ROUND; \
      method0                                  \
      endStyle = LINE_END_BUTT;                \
      method0                                  \
      endStyle = LINE_END_SQUARE;              \
      method0
    
    #define method0 canvas.Erase(ColorToARGB(clrWhite, 255));                                                                                                      \
      canvas.LineThickVertical(point, point, h - point, color_, size, lineStyle, endStyle);                                                                        \
      canvas.LineThickHorizontal(point, w - point, h - point, color_, size, lineStyle, endStyle);                                                                  \
      canvas.LineThick(point * 2, point * 12, point * 8, point * 8, color_, size, lineStyle, endStyle);                                                            \
      canvas.PolylineThick(plX, plY, color_, size, lineStyle, endStyle);                                                                                           \
      canvas.PolygonThick(pgX, pgY, color_, size, lineStyle, endStyle);                                                                                            \
      canvas.TextOut(point * 2, point, "Size: " + (string)size + "; Style: " + EnumToString(lineStyle) + "; End Style: " + EnumToString(endStyle) + ";", color_);  \
      canvas.Update(true);                                                                                                                                         \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {17, 18, 17, 15, 14, 15, 17}, pgY[] = {7, 5, 3, 3, 5, 7, 7};
      ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
      int size = 3;
      ENUM_LINE_END endStyle = LINE_END_ROUND;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      canvas.FontSet("Calibri", -120);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      for (int i = beginSize; i <= endSize; i++) {
        size = i;
        drawPrimitives
      }
      
      Sleep(6000);
      canvas.Destroy();
     }
    //+------------------------------------------------------------------+

    Все изменения в коде, по сравнению с предыдущим примером, выделены желтым. Получившийся в результате скрипт берет диапазон толщины линий из input-параметров beginSize и endSize. И проходит, с помощью макросов, по всем возможным вариантам комбинаций стиля линий и стиля их завершения. В результате получается перебор всех возможных параметров линий примитивов, комбинации которых можно увидеть, запустив скрипт. Результат работы скрипта демонстрирует следующая GIF-анимация.

    Примитивы с различными параметрами

    На анимации мы видим, как изменяются толщина линий, стиль линий и стиль завершения линий. Все эти параметры выводятся на Canvas, и мы также можем их видеть.

    Рисование сглаженных примитивов по алгоритму Безье с изменяемой толщиной линий

    Возьмем один из предыдущих наших примеров DrawPrimitivesThick.mq5 и создадим скрипт на его основе. Назовем его DrawPrimitivesSmooth.mq5. В скрипте заменим методы PolylineThick и PolygonThick на PolylineSmooth и PolygonSmooth. Сместим координаты ломаной линии и полигона на 2 клетки влево, чтобы примитивы выводились примерно по центру.

    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {3, 5, 6, 8, 9, 11}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {14, 15, 14, 12, 11, 12}, pgY[] = {7, 5, 3, 3, 5, 7};

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

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                                      \
      canvas.PolylineSmooth(plX, plY, color_, size, lineStyle, endStyle);                                 \
      canvas.PolygonSmooth(pgX, pgY, color_, size, lineStyle, endStyle);                                  \
      canvas.Update(true);                                                                                \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {3, 5, 6, 8, 9, 11}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {14, 15, 14, 12, 11, 12}, pgY[] = {7, 5, 3, 3, 5, 7};
      ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
      int size = 3;
      ENUM_LINE_END endStyle = LINE_END_BUTT;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.LineStyleSet(STYLE_SOLID);
      drawPrimitives
      
      Sleep(6000);
      canvas.Destroy();
     }

    Запустим получившийся пример и посмотрим на результат.

    Примитивы по алгоритму Безье (Smooth)

    На изображении мы видим ломаную линию и полигон, которые построены с помощью кривых Безье. У методов PolylineSmooth и PolygonSmooth есть параметры tension - сглаживание и step - шаг аппроксимации, согласно которым строятся кривые Безье. Создадим пример, где эти параметры будут меняться с заданными интервалами и на экран будет выводиться результат этих изменений. Возьмём пример DrawPrimitivesThick-2.mq5 и на основе него создадим скрипт DrawPrimitivesSmooth-2.mq5. Чтобы долго не рассматривать процесс изменения кода, сразу посмотрим результат.

    #property script_show_inputs
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //--- input parameters
    input int sleep = 1000;
    input int lineSize = 3;
    input ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
    input ENUM_LINE_END lineEndStyle = LINE_END_BUTT;
    input double minTension = 0.0;
    input double stepTension = 0.1;
    input double maxTension = 1.0;
    input double minStep = 1.0;
    input double stepStep = 5.0;
    input double maxStep = 21.0;
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {3, 5, 6, 8, 9, 11}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {14, 15, 14, 12, 11, 12}, pgY[] = {7, 5, 3, 3, 5, 7};
      ENUM_LINE_STYLE style = lineStyle;
      int size = lineSize;
      ENUM_LINE_END endStyle = lineEndStyle;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      canvas.FontSet("Calibri", -120);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      for (double tension = minTension; tension <= maxTension; tension += stepTension)
        for (double step = minStep; step <= maxStep; step += stepStep)
         {
           canvas.Erase(ColorToARGB(clrWhite, 255));
           canvas.PolylineSmooth(plX, plY, color_, size, style, endStyle, tension, step);
           canvas.PolygonSmooth(pgX, pgY, color_, size, style, endStyle, tension, step);
           canvas.TextOut(point * 2, point, "Size: " + (string)size + "; Style: " + EnumToString(style) + "; End Style: " + EnumToString(endStyle) + ";", color_);
           canvas.TextOut(point * 2, point * 2, "Tension: " + DoubleToString(tension, 2) + ";" + " Step: " + DoubleToString(step, 2) + ";", color_);
           canvas.Update(true);
           Sleep(sleep);
         }
      
      canvas.Destroy();
     }

    Мы видим, что код был почти полностью изменен. Среди input-параметров появились следующие настройки: lineSize - размер линий, lineStyle - стиль линий и lineEndStyle - стиль окончания линий (теперь они не принимают участие в цикле). Также добавлены input-параметры для установки параметров рисования кривых БезьеminTensionmaxTension и stepTension (диапазон и шаг значений параметра сглаживания tension), а также minStepmaxStep и stepStep (диапазон и шаг значений шага аппроксимации step). Далее эти параметры работают в циклах, задавая все возможные комбинации параметров tension и step согласно заданным stepTension и stepStep, соответственно. Запустим пример и посмотрим, что у нас получилось в результате.

    Примитивы с различными параметрами

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

    Рисование закрашенных примитивов

    При разработке приложений, выводящих графику, бывают случаи, когда нужно закрасить тот или иной примитив. В классе CCanvac для подобных случаев существуют методы с префиксом Fill, которые закрашивают соответствующие примитивы в однородный цвет. Возьмем наш предыдущий пример DrawPrimitivesWu.mq5 и создадим на его основе скрипт DrawPrimitivesFill.mq5, где для нужных нам примитивов уже прописаны все координаты и осталось только подставить соответствующие методы и задать цвет заливки. Подставим методы FillPolygonFillTriangleFillCircle и FillEllipse. О методе Fill речь пойдет ниже (когда мы будем рассматривать заливку). Посмотрим код, получившийся в результате.

    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint fillColor = ColorToARGB(clrRed, 255);
      int pgX[] = {4, 5, 7, 8, 7, 5}, pgY[] = {12, 10, 10, 12, 14, 14};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.FillPolygon(pgX, pgY, fillColor);
      canvas.FillTriangle(point * 4, point * 8, point * 4, point * 3, point * 9, point * 3, fillColor); 
      canvas.FillCircle(point * 13, point * 12, point * 2, fillColor);
      canvas.FillEllipse(point * 11, point * 3, point * 15, point * 6, fillColor);
      canvas.Update(true);                                                                         
      
      Sleep(6000);
      canvas.Destroy();
     }

    Все измененные и добавленные строки выделены желтым. Запустим скрипт и посмотрим результат.

    Закрашенные примитивы (Fill) 

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

    Заливка

    Все, кто пользовались инструментом "Заливка" в графических редакторах, наверняка догадываются, о чём сейчас пойдет речь. Итак, пришло время рассмотреть метод Fill, о котором я говорил выше и который как раз и выполняет заливку области изображения определенного цвета другим заданным цветом. Создадим новый пример в виде индикатора (поскольку, он обрабатывает события чарта (OnChartEvent) в отличии от скрипта). Многие спросят, зачем нам в данном случае обработка событий чарта? Мы создадим пример, в котором можно выбирать цвет заливки и, кликая в любой точке изображения, заливать его выбранным цветом. Создадим новый индикатор и назовем его Filling.mq5. Рассмотрим код индикатора.

    #property indicator_chart_window
    #property indicator_plots 0
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    #include <ChartObjects\ChartObjectsTxtControls.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    CCanvas canvas;
    CChartObjectButton colors[14];
    CChartObjectButton * oldSelected = NULL;
    uint fillColor = 0;
    
    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int OnInit()
     {
      int w, h;
      int minSize, point;
      uint Color1 = ColorToARGB(clrDodgerBlue, 255);
      uint Color2 = ColorToARGB(clrRed, 255);
      int pgX[] = {4, 5, 7, 8, 7, 5}, pgY[] = {12, 10, 10, 12, 14, 14};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
    
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      canvas.FontSet("Calibri", -120);
      
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.Polygon(pgX, pgY, Color1);
      canvas.FillTriangle(point * 4, point * 8, point * 4, point * 3, point * 9, point * 3, Color2);
      canvas.FillCircle(point * 13, point * 12, point * 2, Color2);
      canvas.Ellipse(point * 11, point * 3, point * 15, point * 6, Color1);
      canvas.Update(true); 
      
      ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
      
      for (int i = 0; i < (int)colors.Size(); i++)
       {
        colors[i].Create(0, "color-"+(string)i, 0, (int)((i + 2.8) * point), point, 21, 21);
        colors[i].SetInteger(OBJPROP_BGCOLOR, ColorToARGB(CCanvas::GetDefaultColor(i), 0));
       }
      
      if ((oldSelected = GetPointer(colors[(int)colors.Size()-1])) == NULL)
        return(INIT_FAILED); 
      oldSelected.State(1);  
      fillColor = ColorToARGB((color)oldSelected.GetInteger(OBJPROP_BGCOLOR), 255);
      
      return(INIT_SUCCEEDED);
     }
     
    //+------------------------------------------------------------------+
    //| Custom indicator deinitialization function                       |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
     {
      canvas.Destroy();
     }
     
    //+------------------------------------------------------------------+
    //| Custom indicator iteration function                              |
    //+------------------------------------------------------------------+
    int OnCalculate(const int rates_total,
                    const int prev_calculated,
                    const int begin,
                    const double &price[])
     {
    //---
    //--- return value of prev_calculated for next call
      return(rates_total);
     }
    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
     {
      uint mouseState = (uint)sparam;
      int x = (int)lparam, y = (int)dparam;
      CChartObjectButton * colorBtn;
      int left, right, bottom, top;
      
      if (id == CHARTEVENT_MOUSE_MOVE) 
        if ((mouseState & 1) == 1) 
         {
          for (int i = 0; i < (int)colors.Size(); i++) 
           {
            if ((colorBtn = GetPointer(colors[i])) == NULL)
              return;
            left = colorBtn.X_Distance();
            top = colorBtn.Y_Distance();
            right = left + colorBtn.X_Size() - 1;
            bottom = top + colorBtn.Y_Size() - 1;
            if (x >= left && x <= right && y >= top && y <= bottom) {
              fillColor = ColorToARGB((color)colorBtn.GetInteger(OBJPROP_BGCOLOR), 255);
              if (oldSelected == NULL)
                return;
              oldSelected.State(0);
              oldSelected = GetPointer(colorBtn);
              ChartRedraw();
              return;
            }
           }
          canvas.Fill(x, y, fillColor);
          canvas.Update(true);
         }
        
     }

    Как работает пример? Во-первых, переменная canvas была прописана на глобальном уровне для осуществления доступа к ней на всём протяжении работы индикатора.

    CCanvas canvas;

    Для возможности выбора цветов создан массив из 14 кнопок, нажатие одной из которых определяет цвет заливки.

    CChartObjectButton colors[14];

    Далее, объявлен указатель oldSelected.

    CChartObjectButton * oldSelected = NULL;

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

    uint fillColor = 0;

    Далее, в обработчике OnInit мы видим уже знакомый нам код, который создает Canvas и рисует на нем примитивы.

    int OnInit()
     {
      int w, h;
      int minSize, point;
      uint Color1 = ColorToARGB(clrDodgerBlue, 255);
      uint Color2 = ColorToARGB(clrRed, 255);
      int pgX[] = {4, 5, 7, 8, 7, 5}, pgY[] = {12, 10, 10, 12, 14, 14};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      canvas.FontSet("Calibri", -120);
      
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.Erase(ColorToARGB(clrWhite, 255));                                                                                                                 
      canvas.Polygon(pgX, pgY, Color1);                                                          
      canvas.FillTriangle(point * 4, point * 8, point * 4, point * 3, point * 9, point * 3, Color2); 
      canvas.FillCircle(point * 13, point * 12, point * 2, Color2);                                  
      canvas.Ellipse(point * 11, point * 3, point * 15, point * 6, Color1);                     
      canvas.Update(true);
    ...

    Затем включена обработка событий мыши.

    ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

    Далее создаются кнопки разных цветов (для возможности смены цвета заливки).

      for (int i = 0; i < (int)colors.Size(); i++)
       {
        colors[i].Create(0, "color-"+(string)i, 0, (int)((i + 2.8) * point), point, 21, 21);
        colors[i].SetInteger(OBJPROP_BGCOLOR, ColorToARGB(CCanvas::GetDefaultColor(i), 0));
       }

    Предлагаю вам обратить внимание на то, откуда берутся цвета для кнопок. В коде этот фрагмент выделен желтым. Мы видим статический метод CCanvas::GetDefaultColor. Задавая параметр i, начиная от 0, можно получить палитру цветов, что и сделано в данном примере.

    Затем инициализируется ссылка для возврата кнопки oldSelected в исходное состояние.

      if ((oldSelected = GetPointer(colors[(int)colors.Size()-1])) == NULL)
        return(INIT_FAILED); 
      oldSelected.State(1);

    И инициализируется цвет заливки fillColor.

    fillColor = ColorToARGB((color)oldSelected.GetInteger(OBJPROP_BGCOLOR), 255);

    Далее производится обработка событий чарта в OnChartEvent. В нем объявлены переменные для получения и хранения параметров мыши.

    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
     {
      uint mouseState = (uint)sparam;
      int x = (int)lparam, y = (int)dparam;
    ...

    Затем объявлена переменная, указатель для хранения нажатой кнопки,

    CChartObjectButton * colorBtn;

    которая определяется по вхождению курсора в координаты сторон кнопки при нажатии по ней левой кнопкой мыши. Координаты сторон хранятся в следующих переменных.

    int left, right, bottom, top;

    Далее отслеживается событие CHARTEVENT_MOUSE_MOVE и нажатие левой кнопки мыши.

      if (id == CHARTEVENT_MOUSE_MOVE) 
        if ((mouseState & 1) == 1) 
         {
          ...
         }

    Это код, в котором и осуществляется выбор цвета и заливка изображения. Здесь цикл поочередно проходит по всем кнопкам в массиве colors, определяя, по какой кнопке пользователь сделал нажатие, сохраняя цвет этой кнопки в переменную fillColor и возвращая ранее нажатую кнопку oldSelected в исходное положение. Далее производится выход из обработчика (return), поскольку клик был по кнопке, а не по изображению, которое нужно залить. Если клик был сделан но изображению, а не по одной из кнопок colors, управление передаётся далее и осуществляется его заливка с помощью метода Fill выбранным цветом fillColor с последующим обновлением изображения (метод Update).

      if (id == CHARTEVENT_MOUSE_MOVE) 
        if ((mouseState & 1) == 1) 
         {
          for (int i = 0; i < (int)colors.Size(); i++) 
           {
            if ((colorBtn = GetPointer(colors[i])) == NULL)
              return;
            left = colorBtn.X_Distance();
            top = colorBtn.Y_Distance();
            right = left + colorBtn.X_Size() - 1;
            bottom = top + colorBtn.Y_Size() - 1;
            if (x >= left && x <= right && y >= top && y <= bottom) {
              fillColor = ColorToARGB((color)colorBtn.GetInteger(OBJPROP_BGCOLOR), 255);
              if (oldSelected == NULL)
                return;
              oldSelected.State(0);
              oldSelected = GetPointer(colorBtn);
              ChartRedraw();
              return;
            }
           }
          canvas.Fill(x, y, fillColor);
          canvas.Update(true);
         }

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

    Заливка в CCanvas

    Как видно из представленной GIF-анимации, мы создали заливку средствами CCanvas, которая работает аналогично инструменту "Заливка" в графических редакторах.


    Заключение

    В данной статье мы рассмотрели устройство класса CCanvas, изучили его методы, рассмотрели их в порядке, который даёт пользователю полное понимание основных принципов работы класса. Рассмотренные примеры наглядно объясняют принципы работы с классом CCanvas, подтверждая рассмотренный материал и проясняя аспекты, которые сложно понять, читая теоретическую часть статьи.


    Прикрепленные файлы |
    MQL5.zip (21.43 KB)
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
    Ştefan Dascălu
    Ştefan Dascălu | 24 апр. 2022 в 06:39
    Большое спасибо за эту полезную статью
    Шаблон проектирования MVC и возможность его использования (Часть 2): Схема взаимодействия между тремя компонентами Шаблон проектирования MVC и возможность его использования (Часть 2): Схема взаимодействия между тремя компонентами
    Данная статья продолжает и завершает тему, поднятую в прошлой статье — шаблон MVC в программах на MQL. В этой статье мы рассмотрим возможную схему взаимодействия между этими тремя компонентами.
    Веб-проекты (Часть II): Система авторизации Laravel/Nuxt Веб-проекты (Часть II): Система авторизации Laravel/Nuxt
    В этой статье создадим систему авторизации через браузерное приложение и через торговый терминал MetaTrader 5. Можно будет зарегистрироваться в системе, указав свои учётные данные.
    Веб-проекты (Часть III): Система авторизации Laravel/MetaTrader 5 Веб-проекты (Часть III): Система авторизации Laravel/MetaTrader 5
    В этот раз создадим систему авторизации в торговом терминале MetaTrader 5 на чистом MQL5. Пользователи приложения смогут зарегистрироваться в системе, предоставив свои учётные данные, чтобы впоследствии можно было авторизоваться и получить доступ, к каким-нибудь данным, которые хранятся в серверной части приложения.
    Графика в библиотеке DoEasy (Часть 96): Работа с событиями мышки и графика в объектах-формах Графика в библиотеке DoEasy (Часть 96): Работа с событиями мышки и графика в объектах-формах
    В статье начнём разработку функционала для работы с событиями мышки в объектах-формах и добавим новые свойства и их отслеживание в объект-символ. Помимо этого сегодня доработаем класс объекта-символа, так как с момента его написания у символов графика появились новые свойства, которые желательно учитывать и отслеживать их изменение.