![Использование класса CCanvas в MQL приложениях](https://c.mql5.com/2/49/CCanvas_class_600x314.jpg)
Использование класса CCanvas в MQL приложениях
В данной статье рассмотрено устройство класса 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 поделен на два основных раздела - данные и методы.
Рассмотрим данные:
- Пиксельные данные. Представляют собой массив пикселей 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, Polyline, Polygon, 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 - координата первой точки по оси Y
- x2 - координата второй точки по оси X
- 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.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 выделен серым цветом. Запустим скрипт и посмотрим, что получилось.
Мы видим, как изменился цвет изображения. Теперь попробуем изменить прозрачность. Установим значение альфа-канала в значение 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_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_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.
Но стоит уменьшить значение альфа-канала всего на единицу,
canvas.Erase(0xFA98FB98);
как мы тут же заметим довольно существенные изменения финального изображения.
Теперь проверим, как ведет себя изображение при полном изменении прозрачности. Для этого создадим новый пример и назовём его 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 мы увидели, что изображение со значением альфа-канала 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_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(); }
Посмотрим на результат работы скрипта.
Мы видим, что на изображении появился текст.
Изменим наш пример таким образом, чтобы изображение появлялось ровно в центре чарта и напоминало объект 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 будет полностью перекрашивать пиксели чарта).
Теперь установим значение альфа-канала в 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(); }
Запустим скрипт и посмотрим, что у нас получилось.
Мы видим аналог текстовой метки (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(); }
В выделенном фрагменте кода видно, как меняется стиль и вызываются методы рисования. Посмотрим результат работы скрипта.
На данной 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. Теперь раскомментируем строки в нашем скрипте и снова запустим его.
Мы видим, что стили линий точно также меняются, как и в предыдущем примере, с той лишь разницей, что качество сглаживания изображения примитивов более высокое.
Рисование примитивов с изменяемой толщиной линий
Создадим скрипт на основе предыдущего примера и назовём его 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 - стиль завершения линии. Запустим скрипт и посмотрим на получившийся результат.
На изображении мы видим примитивы с толстыми линиями, толщину которых можно изменять, как уже было сказано выше. Создадим новый скрипт на основе предыдущего и назовём его 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(); }
Запустим получившийся пример и посмотрим на результат.
На изображении мы видим ломаную линию и полигон, которые построены с помощью кривых Безье. У методов 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-параметры для установки параметров рисования кривых Безье: minTension, maxTension и stepTension (диапазон и шаг значений параметра сглаживания tension), а также minStep, maxStep и stepStep (диапазон и шаг значений шага аппроксимации step). Далее эти параметры работают в циклах, задавая все возможные комбинации параметров tension и step согласно заданным stepTension и stepStep, соответственно. Запустим пример и посмотрим, что у нас получилось в результате.
На данной GIF-анимации мы видим сглаженные примитивы с меняющимися параметрами сглаживания, значения которых выводятся на изображение, во второй строке.
Рисование закрашенных примитивов
При разработке приложений, выводящих графику, бывают случаи, когда нужно закрасить тот или иной примитив. В классе CCanvac для подобных случаев существуют методы с префиксом Fill, которые закрашивают соответствующие примитивы в однородный цвет. Возьмем наш предыдущий пример DrawPrimitivesWu.mq5 и создадим на его основе скрипт DrawPrimitivesFill.mq5, где для нужных нам примитивов уже прописаны все координаты и осталось только подставить соответствующие методы и задать цвет заливки. Подставим методы FillPolygon, FillTriangle, FillCircle и 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, о котором я говорил выше и который как раз и выполняет заливку области изображения определенного цвета другим заданным цветом. Создадим новый пример в виде индикатора (поскольку, он обрабатывает события чарта (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); }
Теперь предлагаю вам запустить получившийся пример и попробовать выполнить заливку.
Как видно из представленной GIF-анимации, мы создали заливку средствами CCanvas, которая работает аналогично инструменту "Заливка" в графических редакторах.
Заключение
В данной статье мы рассмотрели устройство класса CCanvas, изучили его методы, рассмотрели их в порядке, который даёт пользователю полное понимание основных принципов работы класса. Рассмотренные примеры наглядно объясняют принципы работы с классом CCanvas, подтверждая рассмотренный материал и проясняя аспекты, которые сложно понять, читая теоретическую часть статьи.
![Bellamy Dascălu Bellamy Dascălu](https://c.mql5.com/avatar/2024/8/66b95df1-a6fd.png)
![Шаблон проектирования MVC и возможность его использования (Часть 2): Схема взаимодействия между тремя компонентами](https://c.mql5.com/2/44/MVC.png)
![Веб-проекты (Часть II): Система авторизации Laravel/Nuxt](https://c.mql5.com/2/45/web_project__1.png)
![Веб-проекты (Часть III): Система авторизации Laravel/MetaTrader 5](https://c.mql5.com/2/45/web_project.png)
![Графика в библиотеке DoEasy (Часть 96): Работа с событиями мышки и графика в объектах-формах](https://c.mql5.com/2/45/MQL5-avatar-doeasy-library3-2.png)
![MQL5 - Язык торговых стратегий для клиентского терминала MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования