- Описание ресурсов с помощью директивы #resource
- Разделяемое использование ресурсов разных MQL-программ
- Ресурсные переменные
- Подключение пользовательских индикаторов как ресурсов
- Динамическое создание ресурсов: ResourceCreate
- Удаление динамических ресурсов: ResourceFree
- Чтение и модификация данных ресурса: ResourceReadImage
- Сохранение изображений в файл: ResourceSave
- Шрифты и вывод текста в графические ресурсы
- Прикладное применение графических ресурсов в трейдинге
Сохранение изображений в файл: ResourceSave
MQL5 API позволяет записать ресурс в файл формата BMP с помощью функции ResourceSave. В данный момент среда поддерживает только ресурсы-изображения.
bool ResourceSave(const string resource, const string filename)
В параметрах resource и filename указываются, соответственно, имя ресурса и файла. Имя ресурса должно начинаться с "::". Имя файла может содержать путь относительно папки MQL5/Files. При необходимости функция создаст все промежуточные подкаталоги. Если указанный файл существует, он будет перезаписан.
Функция возвращает true в случае успеха.
Для проверки работы данной функции желательно создать оригинальное изображение. И у нас есть для этого подходящий "материал".
В рамках изучения ООП, в главе Классы и интерфейсы, мы начали серию примеров про графические фигуры: от самой первой версии Shapes1.mq5 в разделе про Определение класса и до последней Shapes6.mq5 в разделе про Вложенные типы. Само рисование тогда не было нам доступно, вплоть до главы про графические объекты, где мы смогли реализовать визуализацию в скрипте ObjectShapesDraw.mq5. Теперь, после изучения графических ресурсов, настало время очередного "апгрейда".
В новой версии скрипта ResourceShapesDraw.mq5 фигуры будут по-настоящему рисоваться. Чтобы было проще анализировать изменения по сравнению с прежней версией, мы оставим тот же набор фигур: прямоугольник, квадрат, овал, круг и треугольник. Это сделано для примера, а не потому что в рисовании нас что-то ограничивает: наоборот — существует потенциал по расширению набора фигур, визуальных эффектов и нанесению надписей. Некоторые из этих возможностей мы рассмотрим в этом примере, некоторые — в следующих, но продемонстрировать все многообразие применений в рамках книги просто невозможно.
После того как фигуры будут сгенерированы и отрисованы, мы сохраним получившийся ресурс в файл.
Напомним, что основой иерархии классов фигур является Shape, в котором был метод draw.
class Shape
|
В производных классах он был реализован на основе графических объектов, с вызовами ObjectCreate и последующей настройкой объектов ObjectSet-функциями. Общим холстом такого рисунка был непосредственно график.
Сейчас нам потребуется закрашивать пиксели в некотором общем ресурсе в соответствии с формой конкретной фигуры. Общий ресурс и методы модификации пикселей в нем желательно выделить в отдельный класс или лучше интерфейс.
Абстрактная сущность позволит не делать увязки со способом создания и настройкой ресурса. В частности, наша последующая реализация поместит ресурс в объект OBJ_BITMAP_LABEL (как мы уже делали в этой главе), а для кого-то может быть достаточно генерировать изображения в памяти и сохранять на диск, не выводя на график (так многие трейдеры любят периодически фиксировать состояния чартов).
Назовем интерфейс Drawing.
interface Drawing
|
Здесь представлены лишь три самых базовых метода для рисования. Нам этого хватит.
Метод point хотя и является публичным (что дает возможность поставить отдельную точку), но в некотором смысле — низкоуровневый, так как через него будут реализованы все остальные. Именно поэтому в нем координаты сделаны вещественными, а содержимое пикселя — готовым значением типа uint. Это позволит при необходимости подключить различные алгоритмы сглаживания, чтобы фигуры не выглядели ступенчатыми из-за пикселизации. Здесь мы не будем касаться этого вопроса.
С учетом интерфейса метод Shape::draw превращается в такой:
virtual void draw(Drawing *drawing) = 0; |
Тогда в классе Rectangle очень просто поручить отрисовку прямоугольника новому интерфейсу.
class Rectangle : public Shape
|
Для рисования эллипса придется потрудиться побольше.
class Ellipse : public Shape
|
Наконец, для треугольника рисование выполнено следующим образом.
class Triangle: public Shape
|
Обратимся теперь к классу MyDrawing — наследнику интерфейса Drawing. Именно MyDrawing должен, руководствуясь вызовами интерфейсных методов в фигурах, обеспечить отображение в растровой картинке некоего ресурса. Поэтому внутри класса, прежде всего, описаны переменные для названий графического объекта (object) и ресурса (sheet), а также массив data типа uint для хранения картинки. Кроме того мы перенесли сюда массив фигур shapes, который ранее просто был объявлен в обработчике OnStart. Поскольку за рисование всех фигур отвечает MyDrawing, то и управлять их набором лучше здесь.
class MyDrawing: public Drawing
|
В конструкторе создаем графический объект на размер всего графика и выделяем память под массив data. Холст заполняется нулями (означает "черную прозрачность") или другим значением, переданным в параметре background, после чего на его основе создается ресурс. По умолчанию имя ресурса начинается с буквы 'D' и включает идентификатор текущего графика, но можно задать другое.
public:
|
Вызывающий код может узнать имя ресурса с помощью метода resource.
string resource() const
|
В деструкторе ресурс и объект удаляются.
~MyDrawing()
|
Для заполнения массива фигур предусмотрен метод push.
Shape *push(Shape *shape)
|
Для их рисования определен метод draw, который просто в цикле вызывает метод draw каждой фигуры, а затем обновляет ресурс и график.
void draw()
|
Осталось рассмотреть самые главные методы — методы интерфейса Drawing, которые собственно и обеспечивают рисование.
Начнем с метода point, который пока приведем в упрощенном виде (усовершенствованиями займемся позднее).
virtual void point(const float x1, const float y1, const uint pixel) override
|
На основе point легко сделать рисование линии. Когда координаты начальной и конечной точки совпадают по одному из измерений, мы делегируем рисование методу rect: ведь прямая линия — это вырожденный случай прямоугольника единичной толщины.
virtual void line(const int x1, const int y1, const int x2, const int y2, const color clr) override
|
А вот и сам метод rect.
virtual void rect(const int x1, const int y1, const int x2, const int y2, const color clr) override
|
Осталось модифицировать обработчик OnStart, и скрипт будет готов.
В начале мы настраиваем график (скрываем все элементы). В принципе, это не обязательно: оставлено для сопоставления со скриптом-прототипом.
void OnStart()
|
Далее описываем объект класса MyDrawing, генерируем предопределенное количество случайных фигур (здесь всё осталось без изменений, включая генератор addRandomShape и макрос FIGURES, равный 21-у), рисуем их в ресурсе и выводим в объекте на графике.
MyDrawing raster;
|
Напомним, что в примере ObjectShapesDraw.mq5 мы затем начинали бесконечный цикл, в котором хаотично двигали фигуры. Повторим этот прием и здесь. Но для него потребуется дополнить класс MyDrawing — он должен этим заняться, раз массив фигур хранится у него внутри. Напишем простой метод shake.
class MyDrawing: public Drawing
|
Тогда в OnStart мы можем задействовать новый метод в цикле, пока пользователь не остановит анимацию.
void OnStart()
|
На этом функционал прежнего примера фактически повторен. Но нам нужно добавить сохранение картинки в файл. Поэтому добавим входной параметр SaveImage.
input bool SaveImage = false; |
Когда он будет установлен в true, проверим функцию ResourceSave в деле.
void OnStart()
|
Кроме того, раз уж речь зашла о входных переменных, позволим пользователю выбирать фон и передадим полученное значение в конструктор MyDrawing.
input color BackgroundColor = clrNONE;
|
Итак, все готово для первого испытания.
Если запустить скрипт ResourceShapesDraw.mq5, на графике сформируется изображение вроде следующего.
Растровое изображение ресурса с набором случайных фигур
При сравнении данного изображения с тем, что мы видели в примере ObjectShapesDraw.mq5, обнаруживается, что наш новый способ визуализации несколько отличается от того, каким терминал выводит объекты. Хотя формы фигур и их цвет в отдельности не вызывают вопросов, места наложения фигур обозначаются по-разному.
Наш скрипт закрашивает фигуры указанным цветом, накладывая их друг на друга в порядке следования в массиве. Более поздние фигуры перекрывают более ранние. Терминал же применяет в местах перекрытия некое смешение цветов (инверсию).
Оба способа имеют право на существование, здесь нет ошибок. Однако интересно, нельзя ли добиться при рисовании аналогичного эффекта?
Благодаря тому, что мы полностью управляем процессом рисования, к нему можно применить любые эффекты, не только такой, как в терминале.
Давайте реализуем, в дополнение к исходному, простому способу рисования, еще несколько режимов. Все они сведены в перечисление COLOR_EFFECT.
enum COLOR_EFFECT
|
Добавим входную переменную для выбора режима.
input COLOR_EFFECT ColorEffect = PLAIN; |
Поддержим режимы в классе MyDrawing. Для начала опишем соответствующие поле и метод.
class MyDrawing: public Drawing
|
Затем усовершенствуем метод point.
virtual void point(const float x1, const float y1, const uint pixel) override
|
Вы можете попробовать запускать скрипт в различных режимах и сравнить результаты. Не забывайте про возможность настройки фона. Вот, например, как выглядит осветление.
Изображение фигур с осветляющим смешиванием цветов
Чтобы наглядно увидеть разницу в эффектах, вы можете отключить рандомизацию цветов и движение фигур. Стандартный способ перекрытия объектов соответствует константе COMPLEMENT.
В качестве финального эксперимента, включите опцию SaveImage. В обработчике OnStart при генерации имени файла с изображением мы теперь используем название текущего режима. Мы должны получить в файле копию изображения на графике.
...
|
Для более изощренных графических построений нашего интерфейса Drawing будет, скорее всего, недостаточно. Поэтому вы можете использовать готовые классы для рисования, поставляемые с MetaTrader 5 или доступные в базе кодов на mql5.com. В частности, загляните в файл MQL5/Include/Canvas/Canvas.mqh.