32-bit Bitmaps with Transparency

 

This is just some information, rather than a question, but I thought I'd share it since I've been wondering how to do this for some time.

The standard BITMAP and BITMAP_LABEL objects allow us to load bitmap images on to the chart, but do not support 32-bit images (with alpha channel).

This means if you want to load an image that has a transparent background, you have to first paint the background of the image the same colour as the background of your chart in a drawing program. This subsequently means if you are coding an indicator that you want to share with others, if they have a different background colour to their chart than you, it's going to show up as an ugly square of the incorrect colour.

By way of an example:

 Original image with transparent background

 Painted with my chart's background colour

 How it looks on my chart

 How it looks on someone else's chart with a white background

 Not to mention that if the image ends up in front of candles even on my chart, it still looks ugly

 

This is where the Canvas class can come to the rescue. It had a method called LoadFromFile, which supports 32-bit bitmaps.

The syntax is simple:

CCanvas canvas();
canvas.CreateBitmapLabel(chartId, subwindowId, chartObjectName, x, y, width, height, COLOR_FORMAT_ARGB_RAW);
canvas.LoadFromFile(file);
canvas.Update();

 

First however we must convert our bitmap to 32-bit alpha format.

There is a free program called Pixelformer which enables us to do this easily. Images can be imported in PNG format with transparency, and exported as 32-bit bitmaps. There is one small caveat - in order to display properly in MT4, the bitmap must be exported from Pixelformer with the "top-down row order" option selected. If this is not checked, only the top half of the bitmap shows up when you try to display it in MT4 (I think this is down to a bug in the section of the Canvas code which tries to flip an image when handling 32-bit format).

 

 

Once this has been done, the result can be seen as follows:

  Arrow displays correctly on dark background

   And against a light background

 And can be superimposed in front of candles without obscuring them

 

If you replace the function LoadFromFile() by the fixed code, you will also be able to load images that were stored as resources. But you must not set the Top-Down-row-order flag, it won´t work.

This way you don´t need to copy the bitmaps into the files folder of any clients.

Enjoy.

Doerk

//+------------------------------------------------------------------+
//| Load data from file                                              |
//+------------------------------------------------------------------+
bool CCanvas::LoadFromFile(const string filename)
  {
   struct BitmapHeader
     {
      ushort            type;
      uint              size;
      uint              reserv;
      uint              offbits;
      uint              imgSSize;
      uint              imgWidth;
      uint              imgHeight;
      ushort            imgPlanes;
      ushort            imgBitCount;
      uint              imgCompression;
      uint              imgSizeImage;
      uint              imgXPelsPerMeter;
      uint              imgYPelsPerMeter;
      uint              imgClrUsed;
      uint              imgClrImportant;
     }      header;
   bool     result=true;
   CFileBin file;
   int      x,y;
   uchar    a,r,g,b;
   uchar    tmp[];
   uint     img_size;
   bool     no_alpha,no_flip=false;
//--- open file
   if(file.Open(filename,FILE_READ)==INVALID_HANDLE)
     {
      //--- Modified by Doerk
      if (!ResourceReadImage(filename,m_pixels,m_width,m_height))
         return(false);
      else
         return (true);
     }
//--- read header
   if(file.ReadStruct(header)!=sizeof(header))
     {
      Print("Failed to read file header");
      file.Close();
      return(false);
     }
   m_width =(int)header.imgWidth;
   m_height=(int)header.imgHeight;
   if(m_height<0)
     {
      m_height=-m_height;
      no_flip=true;
     }
//--- process depending on color depth
   if(header.imgBitCount==32)
     {
      no_alpha=true;
      img_size=file.ReadArray(m_pixels);
      //--- flip image
      if(!no_flip)
         for(y=0;y<m_height/2;y++)
           {
            ArrayCopy(tmp,m_pixels,0,m_width*y,m_width);
            ArrayCopy(m_pixels,m_pixels,m_width*y,m_width*(m_height-y-1),m_width);
            ArrayCopy(m_pixels,tmp,m_width*(m_height-y-1),0,m_width);
           }
      //--- check if at least one pixel has alpha channel
      //--- then leave image as is (consider it as premultiplied ARGB)
      uint i;
      for(i=0;i<img_size;i++)
        {
         //--- there is alpha channel
         if(GETRGBA(m_pixels[i])!=0)
           {
            no_alpha=false;
            break;
           }
        }
      //--- no alpha channel
      if(no_alpha)
        {
         //--- consider image as nontransparent, add alpha channel as 0xFF
         for(i=0;i<img_size;i++)
            m_pixels[i]|=0xFF000000;
        }
      else
        {
         if(m_format==COLOR_FORMAT_ARGB_RAW)
           {
            //--- color components are not processed by terminal (they should be correctly specified by user) 
            //--- convert image to premultiplied ARGB
            for(i=0;i<img_size;i++)
              {
               switch(a=GETRGBA(m_pixels[i]))
                 {
                  case 0xFF:
                     break;
                  case 0x00:
                     m_pixels[i]=0;
                     break;
                  default:
                     r=GETRGBR(m_pixels[i])*a/255;
                     g=GETRGBG(m_pixels[i])*a/255;
                     b=GETRGBB(m_pixels[i])*a/255;
                     m_pixels[i]=ARGB(a,r,g,b);
                     break;
                 }
              }
           }
        }
     }
   else
     {
      //--- 24 bits - change image color depth to 32 bits
      int byte_width;
      //--- allocate memory for pixels
      if(ArrayResize(m_pixels,m_width*m_height)!=-1)
        {
         //--- the number of bytes that define a line of pixels must be multiple of 4
         byte_width=m_width*3;             // number of bytes in line of pixels
         byte_width=(byte_width+3)&~3;     // align line to the 4 byte boundary
         for(y=0;y<m_height;y++)
           {
            if(file.ReadArray(tmp,0,byte_width)!=byte_width)
              {
               result=false;
               break;
              }
            int k,p;
            for(x=0,k=0,p=m_width*(m_height-y-1);x<m_width;x++,p++,k+=3)
              {
               r=tmp[k+2];
               g=tmp[k+1];
               b=tmp[k];
               m_pixels[p]=XRGB(r,g,b);
              }
           }
        }
      else
         result=false;
     }
//--- succeed
   file.Close();
   return(result);
  }
 

Hallo and thank you for this publication,

I have tried the functions. The function to cerate ab BitmapLabel is running,  but the image is not load.

I can see the Object in the Properties on chart and I can select it.

Now I controlled the File to the bmp and copy it for test in same Folder "Expert"

The bmp ist 32bit and I used Pixelformer and same format too.

I hope someone can help me... thank you.



#include "Canvas.mqh"

CCanvas canvas();

canvas.CreateBitmapLabel(0,0,"testbild",50,50,50,50,COLOR_FORMAT_ARGB_RAW);
canvas.LoadFromFile("testbild.bmp");

//string test = canvas.LoadFromFile("testbild.bmp");
//Alert (test); ///////////------------------->>>>>>>>>>>>>>>>>>>OUTPUT IS "FALSE"

canvas.Update();

ChartRedraw(); >>>>>>>>>>> ONLY FOR TEST
 
xtrader68:

Hallo and thank you for this publication,

I have tried the functions. The function to cerate ab BitmapLabel is running,  but the image is not load...


Canvas #include is not necessary for bitmap image with alpha channel.

Bitmaps with alpha channel require #resource followed by path to image or they will not display. This is not found in MQL4 documentation but it's the only way it will work. 

Example - If your image is located inside the folder /MQL4/Files/, then you would use the following: #resource"\\Files\\testbild.bmp";

Then add the image to the chart using whatever code you've created then use "::Files\\testbild.bmp" for your path. 

 
Thanks for your contributions! However, Pixelformer didn't work for me unfortunately. Luckily, even more simply opening the Bitmap that MQL5 couldn't deal with first in Microsoft paint and saving it again as Bitmap (no matter of which sub-type) worked! :)
 
JPS1:

Canvas #include is not necessary for bitmap image with alpha channel.

Bitmaps with alpha channel require #resource followed by path to image or they will not display. This is not found in MQL4 documentation but it's the only way it will work. 

Example - If your image is located inside the folder /MQL4/Files/, then you would use the following: #resource"\\Files\\testbild.bmp";

Then add the image to the chart using whatever code you've created then use "::Files\\testbild.bmp" for your path. 

This is not working on MT4.


CCanvas::LoadFromFile("::images\long.bmp")

returns always false. The bitmap defintely is there, because CreateBitmap works.

The ressource is declared as described:

#resource  "\\Images\\long.bmp"


The bitmaps is 24 bit, without alpha channel. When setting it to 32 bit, the script has a compiler error.

So can't use it at all.


It seems tranparent bitmaps are not possible with MT4.

 

I'll leave the links here.

Implemented work with PNG (2023)

MT5 - https://www.mql5.com/en/code/45439

MT4 - https://www.mql5.com/ru/forum/227736/page84#comment_49544536

Thanks Nikolai Semko

PNG
PNG
  • www.mql5.com
Forget about BMP files like a bad dream. Thanks to this library, you can now use the PNG format, which has a number of advantages, such as being more compact without losing image quality and maintaining transparency.
 

PNG can be embedded inside the code (for example in the MQH file) and already work with it

Working example - https://www.mql5.com/ru/forum/227736/page86#comment_49668436