Ресурсные переменные

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

#resource "путь_имя_файла" as тип_ресурсной_переменной имя_ресурсной_переменной

Вот несколько примеров объявлений:

#resource "data.bin" as int Data[]           // массив типа int с данными из файла data.bin 
#resource "rates.dat" as MqlRates Rates[]    // массив структур MqlRates из файла rates.dat
#resource "data.txt" as string Message       // строка с содержимым файла data.txt
#resource "image.bmp" as bitmap Bitmap1[]    // одномерный массив с пикселями изображения
                                             // из файла image.bmp
#resource "image.bmp" as bitmap Bitmap2[][]  // двумерный массив с тем же изображением

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

Для текстовых файлов (ресурсов типа string) производится автоматическое определение кодировки по наличию BOM-заголовка. Если BOM отсутствует, то кодировка определяется по содержимому файла. Поддерживаются файлы в кодировках ANSI, UTF-8 и UTF-16. При чтении данных из файлов все строки переводятся в Unicode.

Использование ресурсных строковых переменных может существенно облегчить написание программ, которые основаны не только на чистом MQL5, но и дополнительных технологиях. Например, вы можете написать код OpenCL (который поддерживается в MQL5 в качестве расширения) в отдельном файле, а затем включить его в виде строки в ресурсы MQL-программы. В примере большого эксперта мы уже применяли строки-ресурсы для подключения HTML-шаблонов.

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

Тип bitmap описывает одну точку или пиксель изображения и имеет представление 4-x байтового беззнакового целого (uint). Внутри пикселя содержатся 4 байта, которые соответствуют компонентам цвета в формате ARGB или XRGB (одна буква — один байт), где R — красный (Red), G — зеленый (Green), B — синий (Blue), A — прозрачность (альфа-канал), X — байт игнорируется (без прозрачности). Прозрачность может использоваться для различных эффектов при наложении изображений на график и друг на друга.

Определение форматов ARGB и XRGB мы изучим в разделе про динамическое создание графических ресурсов (см. ResourceCreate). Например, для ARGB число в шестнадцатеричном представлении 0xFFFF0000 задает полностью непрозрачный пиксель (старший байт равен 0xFF) красного цвета (следующий байт также равен 0xFF), а следующие байты для зеленой и синей компоненты равны нулю.

Важно отметить, что кодирование цвета пикселей отличается от байтового представления типа color. Напомним, что значение типа color можно записать в шестнадцатеричном виде так: 0x00BBGGRR, где BB, GG, RR — это соответственно синяя, зеленая и красная компоненты (в каждом байте значение 255 дает максимальную интенсивность компоненты). При аналогичной записи пикселя налицо обратный порядок байтов: 0xAARRGGBB. Полная прозрачность получается, когда старший байт (здесь обозначен AA) равен 0, а значение 255 — сплошной цвет. Для перевода цвета color в ARGB формат имеется функция ColorToARGB.

Файлы формата BMP могут иметь различные способы кодирования (если вы их создаете или редактируете в каком-либо редакторе, уточните этот вопрос в документации этой программы). Ресурсы MQL5 поддерживают не все существующие способы кодирования. Проверить, поддерживается ли конкретный файл, можно с помощью функции ResourceCreate. Указание файла неподдерживаемого формата BMP в директиве приведет к ошибке компиляции.

При загрузке файла с 24-битным кодированием цвета для всех пикселей компонента альфа-канала устанавливается в значение 255 (непрозрачный).

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

При загрузке файла с 32-битным кодированием цвета с альфа-каналом никаких манипуляций с пикселями не происходит.

Изображения можно описывать как одномерными, так и двумерными массивами. Это влияет только на способ адресации, но объем занимаемой памяти будет одинаковым. В обоих случаях размеры массива автоматически устанавливаются на основе данных из bmp-файла. Размер одномерного массива будет равен произведению высоты на ширину картинки (height * width), а двумерный массив получит раздельные измерения [height][width]: первый индекс — номер строки, второй — точка в строке.

Внимание! При объявлении ресурса в привязке к ресурсной переменной обращаться к ресурсу можно только через эту переменную, а стандартный способ чтения через имя "::имя_ресурса" (или в более общем случае "путь_имя_файла.ex5::имя_ресурса") больше не работает. Это также означает, что подобные ресурсы нельзя использовать, как разделяемые, из других программ.

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

Индикатор BmpOwner.mq5 содержит описание трех ресурсов:

  • изображение "search1.bmp" с простой директивой #resource и потому доступное из других программ;
  • изображение "search2.bmp" в виде ресурсной переменной-массива типа bitmap и потому недоступное извне;
  • текстовый файл "message.txt" как ресурсная строка для вывода предупреждения пользователю;

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

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

#resource "search1.bmp"
#resource "search2.bmp" as bitmap image[]
#resource "message.txt" as string Message

Все три файла расположены в том же каталоге, где и исходный код индикатора — MQL5/Indicators/MQL5Book/p7/.

Если пользователь попробует запустить индикатор, тот выводит предупреждение и сразу же прекращает работу. Предупреждение содержится в ресурсной переменной-строке Message.

int OnInit()
{
   Alert(Message); // эквивалент следующей строки кода
   // Alert("This indicator is not intended to run, it holds a bitmap resource");
   
   // удаляем индикатор явно, т.к. иначе он остается "висеть" на графике неинициализированным
   ChartIndicatorDelete(00MQLInfoString(MQL_PROGRAM_NAME));
   return INIT_FAILED;
}

Во втором индикаторе BmpUser.mq5, мы попытаемся использовать внешние ресурсы, заданные во входных переменных ResourceOff и ResourceOn, для отображения в объекте OBJ_BITMAP_LABEL.

input string ResourceOff = "BmpOwner.ex5::search1.bmp";
input string ResourceOn = "BmpOwner.ex5::search2.bmp";

По умолчанию состояние объекта отключенное/отжатое ("Off") и для него берется картинка из предыдущего индикатора — "BmpOwner.ex5::search1.bmp". Этот путь и имя ресурса аналогичны полной записи "\\Indicators\\MQL5Book\\p7\\BmpOwner.ex5::search1.bmp". Краткая форма здесь допустима с учетом того, что индикаторы располагаются рядом друг с другом. Если вы впоследствии откроете диалог свойств объекта, то увидите там в полях Bitmap file (On/Off) именно полную запись.

Для нажатого состояния в ResourceOn предлагается прочитать ресурс "BmpOwner.ex5::search2.bmp" (посмотрим, что из этого выйдет).

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

input int X = 25;
input int Y = 25;
input ENUM_BASE_CORNER Corner = CORNER_RIGHT_LOWER;

Непосредственно создание объекта OBJ_BITMAP_LABEL и установка его свойств, включая и имя ресурса в качестве картинки для OBJPROP_BMPFILE, выполняется в OnInit.

const string Prefix = "BMP_";
const ENUM_ANCHOR_POINT Anchors[] =
{
   ANCHOR_LEFT_UPPER,
   ANCHOR_LEFT_LOWER,
   ANCHOR_RIGHT_LOWER,
   ANCHOR_RIGHT_UPPER
};
   
void OnInit()
{
   const string name = Prefix + "search";
   ObjectCreate(0nameOBJ_BITMAP_LABEL000);
   
   ObjectSetString(0nameOBJPROP_BMPFILE0ResourceOn);
   ObjectSetString(0nameOBJPROP_BMPFILE1ResourceOff);
   ObjectSetInteger(0nameOBJPROP_XDISTANCEX);
   ObjectSetInteger(0nameOBJPROP_YDISTANCEY);
   ObjectSetInteger(0nameOBJPROP_CORNERCorner);
   ObjectSetInteger(0nameOBJPROP_ANCHORAnchors[(int)Corner]);
}

Напомним, что при указании картинок в OBJPROP_BMPFILE нажатое состояние обозначается модификатором 0, а отжатое (по умолчанию) — модификатором 1, что несколько неожиданно.

Обработчик OnDeinit удаляет объект при выгрузке индикатора.

void OnDeinit(const int)
{
   ObjectsDeleteAll(0Prefix);
}

Откомпилируем оба индикатора и запустим BmpUser.ex5 с параметрами по умолчанию. На графике должно появиться изображение графического файла search1.bmp (см. слева).

Нормальное (слева) и проблемное (справа) отображение графических ресурсов в объекте на графике

Нормальное (слева) и проблемное (справа) отображение графических ресурсов в объекте на графике

Если щелкнуть мышью на картинке, то есть переключить ей в нажатое состояние, программа попытается обратиться к ресурсу "BmpOwner.ex5::search2.bmp" (который недоступен из-за того, что к нему привязан ресурсный массив bitmap). В результате мы увидим красный квадрат, обозначающий пустой объект без картинки (см. выше, справа). Аналогичная ситуация будет всегда, если во входном параметре указывать путь или имя с заведомо несуществующим или неразделяемым ресурсом. Вы можете создать собственную программу, описать в ней ресурс, ссылающийся на какой-либо существующий bmp-файл, и затем указать во входных параметрах индикатора BmpUser — индикатор сможет вывести картинку на график.