Problem Description
Solution
To draw custom dynamic images, such as heat maps or status icons, in MQL4, you cannot use standard object properties like OBJPROP_COLOR alone. Instead, you must manipulate a pixel array (buffer) and convert it into a graphical resource using the ResourceCreate() function. This resource is then displayed on the chart using an OBJ_BITMAP_LABEL.
Here is the step-by-step logic:
- Define a Pixel Array: Create a one-dimensional
uintarray representing the pixels (Width × Height). - Manipulate Pixels: Calculate the color (ARGB format) for each pixel based on your logic (e.g., RSI value, price change).
- Create Resource: Use
ResourceCreate()to bind the array to a resource name (e.g.,::MyImage). - Display Object: Create an
OBJ_BITMAP_LABELand set itsOBJPROP_BMPFILEproperty to your resource name.
MQL4 Code Example: Dynamic RSI Heat Map
This Expert Advisor creates a dynamic "Heat Map" tile on your chart. The color shifts from Green (Oversold) to Red (Overbought) based on the RSI value, and it displays the text value inside the image.
#property copyright "Copyright 2023, Quantitative Assistant"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
//--- Input Parameters
input int InpWidth = 200; // Image Width
input int InpHeight = 100; // Image Height
input int InpX = 50; // X Position
input int InpY = 50; // Y Position
input int InpRsiPeriod = 14; // RSI Period
//--- Global Variables
uint ExtImgBuffer[]; // The pixel buffer
string ExtResourceName = "::RsiHeatMap"; // Resource name (must start with ::)
string ExtObjectName = "RsiBitmap"; // Chart object name
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// 1. Resize the pixel buffer
ArrayResize(ExtImgBuffer, InpWidth * InpHeight);
// 2. Create the Bitmap Label object on the chart
if(ObjectFind(0, ExtObjectName) < 0)
{
ObjectCreate(0, ExtObjectName, OBJ_BITMAP_LABEL, 0, 0, 0);
ObjectSetInteger(0, ExtObjectName, OBJPROP_XDISTANCE, InpX);
ObjectSetInteger(0, ExtObjectName, OBJPROP_YDISTANCE, InpY);
ObjectSetInteger(0, ExtObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, ExtObjectName, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
// Link the object to the dynamic resource
ObjectSetString(0, ExtObjectName, OBJPROP_BMPFILE, ExtResourceName);
}
// 3. Initial Draw
UpdateHeatMap();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Clean up object and resource to free memory
ObjectDelete(0, ExtObjectName);
ResourceFree(ExtResourceName);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Update the image on every tick
UpdateHeatMap();
}
//+------------------------------------------------------------------+
//| Logic to draw the Heat Map |
//+------------------------------------------------------------------+
void UpdateHeatMap()
{
// --- 1. Calculate Logic (RSI)
double rsi = iRSI(NULL, 0, InpRsiPeriod, PRICE_CLOSE, 0);
// --- 2. Determine Background Color based on Heat
uint bgColor;
// Simple Heat Logic:
// < 30 = Green (Oversold)
// > 70 = Red (Overbought)
// 50 = Gray (Neutral)
// We interpolate colors for smooth transition
if(rsi >= 70) bgColor = ColorToARGB(clrRed, 255); // Opaque Red
else if(rsi <= 30) bgColor = ColorToARGB(clrGreen, 255); // Opaque Green
else if(rsi > 50)
{
// Gradient from Gray to Red
double ratio = (rsi - 50.0) / 20.0;
bgColor = MixColors(clrGray, clrRed, ratio);
}
else
{
// Gradient from Green to Gray
double ratio = (rsi - 30.0) / 20.0;
bgColor = MixColors(clrGreen, clrGray, ratio);
}
// --- 3. Fill the Pixel Buffer
// Reset buffer with the calculated background color
ArrayFill(ExtImgBuffer, 0, InpWidth * InpHeight, bgColor);
// --- 4. Draw Text onto the Buffer
// We use TextOut to draw directly into the pixel array
string text = "RSI: " + DoubleToString(rsi, 2);
// Setup Font
TextSetFont("Arial", -200, FW_BOLD, 0); // -200 = 20pt size
// Calculate text dimensions to center it
uint w, h;
TextGetSize(text, w, h);
int textX = (InpWidth - (int)w) / 2;
int textY = (InpHeight - (int)h) / 2;
// Draw the text (White color)
TextOut(text, textX, textY, TA_LEFT|TA_TOP, ExtImgBuffer, InpWidth, InpHeight, ColorToARGB(clrWhite), COLOR_FORMAT_ARGB_NORMALIZE);
// --- 5. Create/Update the Resource
// This pushes the array data into the graphical resource memory
if(!ResourceCreate(ExtResourceName, ExtImgBuffer, InpWidth, InpHeight, 0, 0, InpWidth, COLOR_FORMAT_ARGB_NORMALIZE))
{
Print("Failed to create resource");
return;
}
// --- 6. Force Chart Redraw
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Helper: Mix two colors based on a ratio (0.0 to 1.0) |
//+------------------------------------------------------------------+
uint MixColors(color c1, color c2, double ratio)
{
if(ratio < 0) ratio = 0;
if(ratio > 1) ratio = 1;
// Extract RGB components
int r1 = (c1 ) & 0xFF;
int g1 = (c1 >> 8 ) & 0xFF;
int b1 = (c1 >> 16) & 0xFF;
int r2 = (c2 ) & 0xFF;
int g2 = (c2 >> 8 ) & 0xFF;
int b2 = (c2 >> 16) & 0xFF;
// Interpolate
int r = (int)(r1 + (r2 - r1) * ratio);
int g = (int)(g1 + (g2 - g1) * ratio);
int b = (int)(b1 + (b2 - b1) * ratio);
// Recombine and add Alpha channel (255 = fully opaque)
return ColorToARGB(RGB(r, g, b), 255);
}
Key Functions Explained
-
uint ExtImgBuffer[]:
This array holds the raw pixel data. The size must beWidth * Height. The format is ARGB (Alpha, Red, Green, Blue). -
ColorToARGB(color, alpha):
Standard MQL4 colors (likeclrRed) do not have transparency information. This function converts them touintwith an Alpha channel.alpha=255: Fully Opaque.alpha=0: Fully Transparent.
-
TextOut(...):
This function draws text directly into youruintarray. This is significantly faster and cleaner than creating separateOBJ_LABELobjects on top of your image. -
ResourceCreate(...):
This is the engine. It takes youruintarray and converts it into a graphical resource in the terminal's memory.- Resource Name: Must start with
::(e.g.,::MyImage). - Color Format: Use
COLOR_FORMAT_ARGB_NORMALIZEto ensure transparency and anti-aliasing (for text) render correctly.
- Resource Name: Must start with
-
OBJ_BITMAP_LABEL:
This is the container object on the chart. You do not set its color or text properties; you only setOBJPROP_BMPFILEto the name of your dynamic resource.
Q&A: Custom Graphics in MQL4
Q: Can I load an external .bmp image and modify it?
A: Yes. You can use ResourceReadImage to load a file from the MQL4/Images folder into a uint array, modify specific pixels (e.g., change a status dot from red to green), and then use ResourceCreate to update it.
Q: Why is my image black or invisible?
A: This usually happens if the Alpha channel is 0. Ensure you use ColorToARGB(color, 255) or manually set the highest 8 bits of the uint to 0xFF. Also, ensure COLOR_FORMAT_ARGB_NORMALIZE is used in ResourceCreate.
Q: Does this consume a lot of CPU?
A: ResourceCreate is relatively fast, but doing it on every tick for large images (e.g., 1920x1080) can cause lag. For small status icons or heat maps (e.g., 200x200), updating on every tick is generally fine. For larger dashboards, consider updating only when OnTimer triggers or when the underlying data changes.