English Русский 中文 Español Deutsch Português
DoEasyライブラリのグラフィックス(第77部): 影オブジェクトクラス

DoEasyライブラリのグラフィックス(第77部): 影オブジェクトクラス

MetaTrader 5 | 4 10月 2021, 17:27
262 0
Artyom Trishkin
Artyom Trishkin

内容


概念

前回の記事でフォームオブジェクトを開発しているときに影を作成するためのテストオブジェクトワークピースを作成して、オブジェクトの影を作成することについて少し触れました。今日は、この概念を拡張し、フォームオブジェクトを定数コンポーネントとして使用できるように改訂します。フォームオブジェクトは、オブジェクトを作成してその上に影フォームを描画して画面に表示することにより、必要に応じてすぐにオブジェクトの影を使用できるようにする必要があります。

オブジェクトの影を描く2つの方法を検討しました。

  1. フォームオブジェクト自体のキャンバスに直接描く
  2. フォームオブジェクトの下にある別のオブジェクトに描く

実装が簡単なため、2番目のオプションを選択しました。このメソッドの欠点は、追加のオブジェクトを管理する必要があることです。この方法の利点は、影が描かれているグラフィック要素を再配置するだけで、あらゆる変更(たとえば、影の位置座標の変更)をすばやく実装できることです。

フォームオブジェクトに影を描画した場合、影を使用してフォーム全体を完全に再描画する必要がある(または、影を消去し、新しい座標を再計算して再描画する)ため、計算量が多くなります。さらに、影はその下にあるオブジェクトに適用する必要があります。これは、影がオブジェクトと重なる場所での色の結合と透明度の再計算、およびオブジェクトの影が投影される背景のピクセルごとの再描画を意味します。影に別のオブジェクトを使用する場合、これを行う必要はなくなります。別のオブジェクトに描かれた影は、独自の色と透明度を備えており、私たちが介入せずに、その下にあるオブジェクトに重ねられます。ターミナルが私たちのためにすべてを計算します。

もちろん、フォームキャンバスに直接影を描く方法には利点がありますが、実装と制御が簡単なため、2番目のオプションを使用します。最初の影オブジェクトの実装では、ALGLIB数値解析ライブラリを使用したガウスぼかし法を使用します。影を構築するためのその使用法のいニュアンスのいくつかは、「キャンバスクラスの学習。アンチエイリアスと影」稿(Vladimir Karputov著)で説明されています。彼の記事で説明されているガウスぼかし法を適用してみましょう。

影オブジェクトは、フォームオブジェクトを作成するときと同じ方法でグラフィック要素オブジェクトクラスから継承された新しいクラスになります。これらのオブジェクトはすべて、他の多くのオブジェクトと同様に、基本的なグラフィック要素の子孫です。フォームオブジェクトでは、影オブジェクトをすばやく作成し、そのプロパティを変更するためのメソッドを作成します。いつものように、すでに書かれたライブラリクラスを変更しましょう。


ライブラリクラスの改善

\MQL5\Include\DoEasy\Data.mqhで、新しいメッセージのインデックスを追加します

   MSG_LIB_SYS_FAILED_DRAWING_ARRAY_RESIZE,           // Failed to change the array size of drawn buffers
   MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE,            // Failed to change the color array size
   MSG_LIB_SYS_FAILED_ARRAY_RESIZE,                   // Failed to change the array size
   MSG_LIB_SYS_FAILED_ADD_BUFFER,                     // Failed to add buffer object to the list
   MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ,              // Failed to create \"Indicator buffer\" object

...

//--- CChartObjCollection
   MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION,        // Chart collection
   MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,  // Failed to create a new chart object
   MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,         // Failed to add a chart object to the collection
   MSG_CHART_COLLECTION_ERR_CHARTS_MAX,               // Cannot open new chart. Number of open charts at maximum
   MSG_CHART_COLLECTION_CHART_OPENED,                 // Chart opened
   MSG_CHART_COLLECTION_CHART_CLOSED,                 // Chart closed
   MSG_CHART_COLLECTION_CHART_SYMB_CHANGED,           // Chart symbol changed
   MSG_CHART_COLLECTION_CHART_TF_CHANGED,             // Chart timeframe changed
   MSG_CHART_COLLECTION_CHART_SYMB_TF_CHANGED,        // Chart symbol and timeframe changed
  
//--- CForm
   MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,// No shadow object. Create it using the CreateShadowObj() method
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ,      // Failed to create a new shadow object
   
//--- CShadowObj
   MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE,               // Error! Image size too small or blur too extensive
   
  };
//+------------------------------------------------------------------+

また、新しく追加したインデックスに対応するメッセージテキストも追加します

   {"Не удалось изменить размер массива рисуемых буферов","Failed to resize drawing buffers array"},
   {"Не удалось изменить размер массива цветов","Failed to resize color array"},
   {"Не удалось изменить размер массива ","Failed to resize array "},
   {"Не удалось добавить объект-буфер в список","Failed to add buffer object to list"},
   {"Не удалось создать объект \"Индикаторный буфер\"","Failed to create object \"Indicator buffer\""},

...

//--- CChartObjCollection
   {"Коллекция чартов","Chart collection"},
   {"Не удалось создать новый объект-чарт","Failed to create new chart object"},
   {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"},
   {"Нельзя открыть новый график, так как количество открытых графиков уже максимальное","You cannot open a new chart, since the number of open charts is already maximum"},
   {"Открыт график","Open chart"},
   {"Закрыт график","Closed chart"},
   {"Изменён символ графика","Changed chart symbol"},
   {"Изменён таймфрейм графика","Changed chart timeframe"},
   {"Изменён символ и таймфрейм графика","Changed the symbol and timeframe of the chart"},
   
//--- CForm
   {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj () method"},
   {"Не удалось создать новый объект для тени","Failed to create new object for shadow"},
   
//--- CShadowObj
   {"Ошибка! Размер изображения очень маленький или очень большое размытие","Error! Image size is very small or very large blur"},
      
  };
//+---------------------------------------------------------------------+

前回の記事では、影を描くために、フォームオブジェクトの周囲に各辺に5ピクセルのサイズの空のスペースを残しました。結局のところ、通常のガウスぼかしにはより多くのスペースが必要です。経験的に、ぼかし半径が4ピクセルの場合、両側に16ピクセルの空きスペースを残す必要があることがわかりました。ピクセルが少ないと、影が描画されるキャンバスのエッジに沿ってアーティファクト(影がすでに完全に透明で実際には存在しない背景の汚染)が発生します。

\MQL5\Include\DoEasy\Defines.mqhで、影のデフォルトのフリースペースのサイズを16に設定します(以前の設定値である5ではなく)。

//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define NULL_COLOR                     (0x00FFFFFF)               // Zero for the canvas with the alpha channel
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the workspace
//+------------------------------------------------------------------+

新しい型(Shadowオブジェクト)をグラフィック要素タイプの列挙に追加します

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
  };
//+------------------------------------------------------------------+

新しい影オブジェクトを作成するときにはこの型を指定します。これにより、後ですべての影オブジェクトを選択し、それらを同時に処理できるようになります。

ここで作成する影オブジェクトは、フォームオブジェクトによって投影される影の外観に影響を与える独自のプロパティを備えています。
これらのパラメータを\MQL5\Include\DoEasy\GraphINI.mqhのフォームスタイル設定に追加しましょう

//+------------------------------------------------------------------+
//| List of form style parameter indices                             |
//+------------------------------------------------------------------+
enum ENUM_FORM_STYLE_PARAMS
  {
   FORM_STYLE_FRAME_WIDTH_LEFT,                 // Form frame width to the left
   FORM_STYLE_FRAME_WIDTH_RIGHT,                // Form frame width to the right
   FORM_STYLE_FRAME_WIDTH_TOP,                  // Form frame width on top
   FORM_STYLE_FRAME_WIDTH_BOTTOM,               // Form frame width below
   FORM_STYLE_FRAME_SHADOW_OPACITY,             // Shadow opacity
   FORM_STYLE_FRAME_SHADOW_BLUR,                // Shadow blur
   FORM_STYLE_DARKENING_COLOR_FOR_SHADOW,       // Form shadow color darkening
   FORM_STYLE_FRAME_SHADOW_X_SHIFT,             // Shadow X axis shift
   FORM_STYLE_FRAME_SHADOW_Y_SHIFT,             // Shadow Y axis shift
  };
#define TOTAL_FORM_STYLE_PARAMS        (9)      // Number of form style parameters
//+------------------------------------------------------------------+
//| Array containing form style parameters                           |
//+------------------------------------------------------------------+
int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]=
  {
//--- "Flat form" style parameters
   {
      3,                                        // Form frame width to the left
      3,                                        // Form frame width to the right
      3,                                        // Form frame width on top
      3,                                        // Form frame width below
      80,                                       // Shadow opacity
      4,                                        // Shadow blur
      80,                                       // Form shadow color darkening
      2,                                        // Shadow X axis shift
      2,                                        // Shadow Y axis shift
   },
//--- "Embossed form" style parameters
   {
      4,                                        // Form frame width to the left
      4,                                        // Form frame width to the right
      4,                                        // Form frame width on top
      4,                                        // Form frame width below
      80,                                       // Shadow opacity
      4,                                        // Shadow blur
      80,                                       // Form shadow color darkening
      2,                                        // Shadow X axis shift
      2,                                        // Shadow Y axis shift
   },
  };
//+------------------------------------------------------------------+

Shadow blurは、画像のぼかしの半径を設定します。
Form shadow color darkeningは、影の色を暗くするポイントの数を設定します。これは、影の色がチャートの背景色に依存する場合に必要です。この場合、チャートの背景色は灰色に変換され、ここで指定された量だけ暗くなります。

Shadow X/Y axis shiftsは、影をキャストするオブジェクトの中心から影がどれだけシフトするかを示します。ゼロは、影がオブジェクトの周囲にあることを意味します。正の値は、影がオブジェクトに対して右下にシフトすることを示し、負の値は、影が左上にシフトすることを示します。

パラメータの数を変更したので、これを明示的に示す必要があります。以前に使用した5ではなく、新しく 9を設定します。

また、配色設定にさらに別のパラメータ「Form outline rectangle color」を追加します。
フォームをより明確に表示するために、フォームの周囲のフレームを完成させます(フォームフレームと混同しないでください)。これは、外部の背景に対してフォームをその色で強調表示する単純な長方形です。この設定では、長方形の色を指定します。

//+------------------------------------------------------------------+
//| List of indices of color scheme parameters                       |
//+------------------------------------------------------------------+
enum ENUM_COLOR_THEME_COLORS
  {
   COLOR_THEME_COLOR_FORM_BG,                   // Form background color
   COLOR_THEME_COLOR_FORM_FRAME,                // Form frame color
   COLOR_THEME_COLOR_FORM_RECT_OUTER,           // Form outline rectangle color
   COLOR_THEME_COLOR_FORM_SHADOW,               // Form shadow color
  };
#define TOTAL_COLOR_THEME_COLORS       (4)      // Number of parameters in the color theme
//+------------------------------------------------------------------+
//| The array containing color schemes                               |
//+------------------------------------------------------------------+
color array_color_themes[TOTAL_COLOR_THEMES][TOTAL_COLOR_THEME_COLORS]=
  {
//--- Parameters of the "Blue steel" color scheme
   {
      C'134,160,181',                           // Form background color
      C'134,160,181',                           // Form frame color
      clrDimGray,                               // Form outline rectangle color
      clrGray,                                  // Form shadow color
   },
//--- Parameters of the "Light cyan gray" color scheme
   {
      C'181,196,196',                           // Form background color
      C'181,196,196',                           // Form frame color
      clrGray,                                  // Form outline rectangle color
      clrGray,                                  // Form shadow color
   },
  };
//+------------------------------------------------------------------+


\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhのグラフィック要素クラスを改善しましょう。

クラスのpublicセクションには、指定された量だけ色の明度を変更するChangeColorLightness()メソッドがあります。
このメソッドは、変更される色をARGB形式で受け取ります。これは不便な場合があるので、「color」形式の色と不透明度を受け取るオーバーロードされたメソッドを宣言しましょう。

//--- Update the coordinates (shift the canvas)
   bool              Move(const int x,const int y,const bool redraw=false);

//--- Change the brightness of (1) ARGB and (2) COLOR by a specified amount
   uint              ChangeColorLightness(const uint clr,const double change_value);
   color             ChangeColorLightness(const color colour,const uchar opacity,const double change_value);

また、彩度を変更するメソッドも必要です。たとえば、任意の色から灰色を作成するには、彩度コンポーネント(HSL、HSI、HSV、HSB形式のS)を左(ゼロ)にシフトする必要があります。色は完全に不飽和化され、影を描くために必要な灰色の色合いになります。

彩度を変更する2つのオーバーロードされたメソッドを宣言します

//--- Change the brightness of (1) ARGB and (2) COLOR by a specified amount
   uint              ChangeColorLightness(const uint clr,const double change_value);
   color             ChangeColorLightness(const color colour,const double change_value);
//--- Change the saturation of (1) ARGB and (2) COLOR by a specified amount
   uint              ChangeColorSaturation(const uint clr,const double change_value);
   color             ChangeColorSaturation(const color colour,const double change_value);
   
protected:

宣言されたメソッドをクラス本体の外側に実装します。

The method changing the ARGB color saturation by a specified amount:

//+------------------------------------------------------------------+
//| Change the ARGB color saturation by a specified amount           |
//+------------------------------------------------------------------+
uint CGCnvElement::ChangeColorSaturation(const uint clr,const double change_value)
  {
   if(change_value==0.0)
      return clr;
   double a=GETRGBA(clr);
   double r=GETRGBR(clr);
   double g=GETRGBG(clr);
   double b=GETRGBB(clr);
   double h=0,s=0,l=0;
   CColors::RGBtoHSL(r,g,b,h,s,l);
   double ns=s+change_value*0.01;
   if(ns>1.0) ns=1.0;
   if(ns<0.0) ns=0.0;
   CColors::HSLtoRGB(h,ns,l,r,g,b);
   return ARGB(a,r,g,b);
  }
//+------------------------------------------------------------------+

ここで、 uint値として取得された色をそのコンポーネント(アルファチャネル、赤、緑、青)に分解します
第75部で説明されているCColorsクラスのRGBtoHSL()メソッドを使用して、RGBカラーをHSLカラーモデルに変換します。このモデルでは、Sコンポーネント(彩度)が必要です。次に、メソッドに追加された値を加算し、飽和値に0.01を掛けるだけで、新しい飽和度が計算できます。得られた結果が許容値の範囲(0~1)を超えているかどうかを確認します。次に、CColorsクラスとそのHSLtoRGBメソッドを使用して、Hカラーコンポーネントである新しいSとLをRGB形式に変換します
取得したRGBカラーを元のカラーのアルファチャネルで返します

メソッドに渡される彩度を変更するために使用される値に0.01を掛けるのはなぜでしょうか。便利だからです。HSLカラーモデルでは、コンポーネントの値が0から1に変わります。したがって、これらの値を100の倍数で渡す方が便利です(0.01の代わりに1、0.1の代わりに10、1の代わりに100)。さらに重要なことに、すべての値は、さまざまなフォームまたはテキストの彩度の変化の値が存在する可能性があるフォームスタイルの整数として設定されます。

以下は、指定された量だけCOLORの彩度を変更するメソッドです。

//+------------------------------------------------------------------+
//| Change the COLOR saturation by a specified amount                |
//+------------------------------------------------------------------+
color CGCnvElement::ChangeColorSaturation(const color colour,const double change_value)
  {
   if(change_value==0.0)
      return colour;
   uint clr=::ColorToARGB(colour,0);
   double r=GETRGBR(clr);
   double g=GETRGBG(clr);
   double b=GETRGBB(clr);
   double h=0,s=0,l=0;
   CColors::RGBtoHSL(r,g,b,h,s,l);
   double ns=s+change_value*0.01;
   if(ns>1.0) ns=1.0;
   if(ns<0.0) ns=0.0;
   CColors::HSLtoRGB(h,ns,l,r,g,b);
   return CColors::RGBToColor(r,g,b);
  }
//+------------------------------------------------------------------+

メソッドのロジックは、上記で説明したものと似ています。唯一の違いは、ここでは不透明度パラメータが色とその不透明度をARGB色に変換するためにのみ必要なことです。アルファチャンネルは他の場所では使用されていないので、変換中には無視して、ゼロを渡すことができます。次に、ARGBカラーからR、G、Bコンポーネントを抽出し、それらをHSLカラーモデルに変換し、メソッドに渡された値でSコンポーネントを変更し、HSLモデルをRGBに戻し、「color」形式の色に変換されたRGBカラーモデルを返します

以下は、指定された量だけCOLORの明るさを変更するメソッドです。

//+------------------------------------------------------------------+
//| Change the COLOR brightness by a specified amount                |
//+------------------------------------------------------------------+
color CGCnvElement::ChangeColorLightness(const color colour,const double change_value)
  {
   if(change_value==0.0)
      return colour;
   uint clr=::ColorToARGB(colour,0);
   double r=GETRGBR(clr);
   double g=GETRGBG(clr);
   double b=GETRGBB(clr);
   double h=0,s=0,l=0;
   CColors::RGBtoHSL(r,g,b,h,s,l);
   double nl=l+change_value*0.01;
   if(nl>1.0) nl=1.0;
   if(nl<0.0) nl=0.0;
   CColors::HSLtoRGB(h,s,nl,r,g,b);
   return CColors::RGBToColor(r,g,b);
  }
//+------------------------------------------------------------------+

このメソッドは、HSLカラーモデルのLコンポーネントを変更することを除いて、上記で検討したメソッドと同じです。

検討したすべてのメソッドで、色成分を変更する値に0.01を乗算するので、以前に開発したARGB色の明るさを指定した値で変更するメソッドを変更する必要があります

//+------------------------------------------------------------------+
//| Change the ARGB color brightness by a specified value            |
//+------------------------------------------------------------------+
uint CGCnvElement::ChangeColorLightness(const uint clr,const double change_value)
  {
   if(change_value==0.0)
      return clr;
   double a=GETRGBA(clr);
   double r=GETRGBR(clr);
   double g=GETRGBG(clr);
   double b=GETRGBB(clr);
   double h=0,s=0,l=0;
   CColors::RGBtoHSL(r,g,b,h,s,l);
   double nl=l+change_value*0.01;
   if(nl>1.0) nl=1.0;
   if(nl<0.0) nl=0.0;
   CColors::HSLtoRGB(h,s,nl,r,g,b);
   return ARGB(a,r,g,b);
  }
//+------------------------------------------------------------------+

クラスのpublicセクションのオブジェクトプロパティへの簡略化されたアクセスのためのメソッドのブロックは、フォームの影を使用する必要性を示すフラグを設定する宣言されたメソッドを備えています。ただし、何らかの理由でメソッドが実装されていないので、これを修正しましょう

//--- Set the flag of (1) object moveability, (2) activity, (3) element ID, (4) element index in the list and (5) shadow presence
   void              SetMovable(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag);                     }
   void              SetActive(const bool flag)                { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag);                      }
   void              SetID(const int id)                       { this.SetProperty(CANV_ELEMENT_PROP_ID,id);                            }
   void              SetNumber(const int number)               { this.SetProperty(CANV_ELEMENT_PROP_NUM,number);                       }
   void              SetShadow(const bool flag)                { this.m_shadow=flag;                                                   }
   
//--- Return the shift (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area

現在作成しているすべてのフォームオブジェクトは、フレームが追加されているため、立体感があります。フレームは、照らされたエッジがきらめき、照らされていないエッジが暗くなるので、立体感が生まれますが、これだけでは不十分です。先に進んで、3次元の錯覚で背景を作成する機能を追加しましょう。これを実現するには、背景を少なくとも2色(暗い色から明るい色まで)のグラデーションで塗りつぶす必要があります。フォームを新鮮な色で再生させるのには、元の色の明るさのわずかな変化、元の色と明るい色の滑らかなブレンド、そして影で十分です。


フォームをクリアして色で塗りつぶす2つのメソッドはすでに実装しています。背景をグラデーションカラーで塗りつぶすには、別のErase()メソッドを宣言します

//+------------------------------------------------------------------+
//| The methods of filling, clearing and updating raster data        |
//+------------------------------------------------------------------+
//--- Clear the element filling it with color and opacity
   void              Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   void              Erase(color &colors[],const uchar opacity,const bool vgradient=true,const bool cycle=false,const bool redraw=false);
//--- Clear the element completely
   void              Erase(const bool redraw=false);
//--- Update the element
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }

クラス本体の外側で実装しましょう。

//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(color &colors[],const uchar opacity,const bool vgradient=true,const bool cycle=false,const bool redraw=false)
  {
//--- Check the size of the color array
   int size=::ArraySize(colors);
//--- If there are less than two colors in the array
   if(size<2)
     {
      //--- if the array is empty, erase the background completely and leave
      if(size==0)
        {
         this.Erase(redraw);
         return;
        }
      //--- in case of one color, fill the background with this color and opacity, and leave
      this.Erase(colors[0],opacity,redraw);
      return;
     }
//--- Declare the receiver array
   color out[];
//--- Set the gradient size depending on the filling direction (vertical/horizontal)
   int total=(vgradient ? this.Height() : this.Width());
//--- and get the set of colors in the receive array
   CColors::Gradient(colors,out,total,cycle);
   total=::ArraySize(out);
//--- In the loop by the number of colors in the array
   for(int i=0;i<total;i++)
     {
      //--- depending on the filling direction
      switch(vgradient)
        {
         //--- Horizontal gradient - draw vertical segments from left to right with the color from the array
         case false :
            DrawLineVertical(i,0,this.Height()-1,out[i],opacity);
           break;
         //--- Vertical gradient - draw horizontal segments downwards with the color from the array
         default:
            DrawLineHorizontal(0,this.Width()-1,i,out[i],opacity);
           break;
        }
     }
//--- If specified, update the canvas
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

メソッドロジック全体は、コードのコメントで説明されています。このメソッドは、塗りつぶされた色の配列、不透明度の値、垂直方向のグラデーションフラグ( true の場合、塗りつぶしは下向きに実行されます。 false の場合、左から右へ)、ループフラグ( 設定されている場合、塗りつぶしは開始時と同じ色で終了します)と、塗りつぶし後にキャンバスを再描画する必要があることを示すフラグ。カラー配列を取得するには、CColorsクラスのGradient()メソッドを使用します。

これで、ライブラリクラスの変更と追加が完了しました。次に、グラフィック要素オブジェクトクラスの子孫となる影オブジェクトの新しいクラスを作成しましょう。


影オブジェクトクラス

\MQL5\Include\DoEasy\Objects\Graph\ディレクトリで、CShadowObjクラスの新しいファイルShadowObj.mqhを作成します。

グラフィック要素のファイルALGLIB数値解析のライブラリファイルをファイルに含める必要があります。クラスはグラフィック要素オブジェクトクラスから継承する必要があります

//+------------------------------------------------------------------+
//|                                                    ShadowObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "GCnvElement.mqh"
#include <Math\Alglib\alglib.mqh>
//+------------------------------------------------------------------+
//| Shadow object class                                              |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
  }

クラスのprivateセクションで、色と影の不透明度を格納するための変数と、クラス操作のメソッドを宣言します。

//+------------------------------------------------------------------+
//| Shadow object class                                              |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color_shadow;                  // Shadow color
   uchar             m_opacity_shadow;                // Shadow opacity
   
//--- Gaussian blur
   bool              GaussianBlur(const uint radius);
//--- Return the array of weight ratios
   bool              GetQuadratureWeights(const double mu0,const int n,double &weights[]);
//--- Draw the object shadow form
   void              DrawShadowFigureRect(const int w,const int h);

public:

ここで、DrawShadowFigureRect()メソッドは、このオブジェクトによって描画された影を落とすフォームオブジェクトの寸法に従って、ぼやけていない形状を描画します。
GetQuadratureWeights()メソッドは、ALGLIBライブラリを適用して、DrawShadowFigureRect()メソッドによって描画された形状をぼかすために使用される重み比の配列を計算して返します。
形状のぼかしは、GaussianBlur()メソッドによって実行されます。
すべてのメソッドは以下で検討されています。

クラスのpublicセクションで、パラメータコンストラクタオブジェクトプロパティをサポートするためのフラグを返すメソッド(両方のメソッドがtrueを返すまで) 、影を描画するためのメソッドを宣言し、影オブジェクトのプロパティへの簡略化されたアクセスのメソッドを記述します

public:
                     CShadowObj(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h);

//--- Supported object properties (1) integer and (2) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }

//--- Draw an object shadow
   void              DrawShadow(const int shift_x,const int shift_y,const uchar blur_value);
   
//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) return the shadow color
   void              SetColorShadow(const color colour)                       { this.m_color_shadow=colour;    }
   color             ColorShadow(void)                                  const { return this.m_color_shadow;    }
//--- (1) Set and (2) return the shadow opacity
   void              SetOpacityShadow(const uchar opacity)                    { this.m_opacity_shadow=opacity; }
   uchar             OpacityShadow(void)                                const { return this.m_opacity_shadow;  }
  };
//+------------------------------------------------------------------+


クラスメソッドの構造について詳しく見ていきましょう。

以下は、パラメトリックコンストラクタです。

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h)
  {
   CGCnvElement::SetColorBackground(clrNONE);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.m_opacity_shadow=127;
   color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100);
   this.m_color_shadow=CGCnvElement::ChangeColorLightness(gray,255,-50);
   this.m_shadow=false;
   this.m_visible=true;
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+

コンストラクタは、グラフのID、影オブジェクトの作成に使用されるサブウィンドウのインデックス、その名前、左上のフォーム角度の座標、およびそのサイズを受け取ります。初期化リストで、要素の型(影オブジェクト)およびメソッド引数で渡されたその他のパラメータを、グラフィック要素クラスのprotectedコンストラクタに渡します。

コンストラクタ本体で、オブジェクトの背景の不在完全な透明度オブジェクトの非アクティブフラグを設定します(影オブジェクトは外部の影響に反応してはなりません)。キャンバスに描画される影のデフォルトの不透明度は127に設定されています。これは半透明の影です。次に、デフォルトの影の色を計算します。チャートの背景色が100のうち50単位暗くされています。ここでは、最初にチャートの背景色を灰色の色合いに変換してから、結果の色を暗くします。影が描かれているオブジェクトはそれをキャストしてはならないため、影フラグをfalseに設定し、オブジェクトの可視性フラグをtrueに設定し、キャンバスをクリアします。

以下は、影オブジェクトを描画するメソッドです。

//+------------------------------------------------------------------+
//| Draw the object shadow                                           |
//+------------------------------------------------------------------+
void CShadowObj::DrawShadow(const int shift_x,const int shift_y,const uchar blur_value)
  {
//--- Calculate the height and width of the drawn rectangle
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Draw a filled rectangle with calculated dimensions
   this.DrawShadowFigureRect(w,h);
//--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant
   int radius=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal)
   if(!this.GaussianBlur(radius))
      return;
//--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas
   CGCnvElement::Move(this.CoordX()+shift_x,this.CoordY()+shift_y);
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

メソッドロジック全体は、コードへのコメントで説明されています。このメソッドでは、最初に影の色で塗りつぶされた通常の長方形を描画します。長方形の幅と高さは、影を落とすフォームオブジェクトのサイズになるように計算されます。次に、ガウスの方法を使用して描画された長方形をぼかし、この影を投影する形状オブジェクトに対して指定されたオフセットだけ影オブジェクトをシフトし、影オブジェクトのキャンバスを更新します。

以下は、オブジェクトの影の形を描くメソッドです。

//+------------------------------------------------------------------+
//| Draw the object shadow form                                      |
//+------------------------------------------------------------------+
void CShadowObj::DrawShadowFigureRect(const int w,const int h)
  {
   CGCnvElement::DrawRectangleFill(OUTER_AREA_SIZE,OUTER_AREA_SIZE,OUTER_AREA_SIZE+w-1,OUTER_AREA_SIZE+h-1,this.m_color_shadow,this.m_opacity_shadow);
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

ここでは、OUTER_AREA_SIZE定数値に等しいX/Y座標で長方形を描画します。2番目のX/Y座標は、最初の座標からのオフセット+幅(高さ)から1を引いたものとして計算されます。形状を描画した後、キャンバスが更新されます。

以下は、ガウスの方法を使用して描画された形状をぼかすメソッドです。

//+------------------------------------------------------------------+
//| Gaussian blur                                                    |
//| https://www.mql5.com/ja/articles/1612#chapter4                   |
//+------------------------------------------------------------------+
bool CShadowObj::GaussianBlur(const uint radius)
  {
//---
   int n_nodes=(int)radius*2+1;
   uint res_data[];              // Array for storing graphical resource data
   uint res_w=this.Width();      // Graphical resource width
   uint res_h=this.Height();     // Graphical resource height
   
//--- Read graphical resource data. If failed, return false
   ::ResetLastError();
   if(!::ResourceReadImage(this.NameRes(),res_data,res_w,res_h))
     {
      CMessage::OutByID(MSG_LIB_SYS_FAILED_GET_DATA_GRAPH_RES);
      return false;
     }
//--- Check the blur amount. If the blur radius exceeds half of the width or height, return 'false'
   if(radius>=res_w/2 || radius>=res_h/2)
     {
      ::Print(DFUN,CMessage::Text(MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE));
      return false;
     }
     
//--- Decompose image data from the resource into a, r, g, b color components
   int  size=::ArraySize(res_data);
//--- arrays for storing A, R, G and B color components
//--- for horizontal and vertical blur
   uchar a_h_data[],r_h_data[],g_h_data[],b_h_data[];
   uchar a_v_data[],r_v_data[],g_v_data[],b_v_data[];
   
//--- Change the size of component arrays according to the array size of the graphical resource data
   if(::ArrayResize(a_h_data,size)==-1)
     {
      CMessage::OutByID(MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"a_h_data\"");
      return false;
     }
   if(::ArrayResize(r_h_data,size)==-1)
     {
      CMessage::OutByID(MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"r_h_data\"");
      return false;
     }
   if(::ArrayResize(g_h_data,size)==-1)
     {
      CMessage::OutByID(MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"g_h_data\"");
      return false;
     }
   if(ArrayResize(b_h_data,size)==-1)
     {
      CMessage::OutByID(MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"b_h_data\"");
      return false;
     }
   if(::ArrayResize(a_v_data,size)==-1)
     {
      CMessage::OutByID(MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"a_v_data\"");
      return false;
     }
   if(::ArrayResize(r_v_data,size)==-1)
     {
      CMessage::OutByID(MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"r_v_data\"");
      return false;
     }
   if(::ArrayResize(g_v_data,size)==-1)
     {
      CMessage::OutByID(MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"g_v_data\"");
      return false;
     }
   if(::ArrayResize(b_v_data,size)==-1)
     {
      CMessage::OutByID(MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"b_v_data\"");
      return false;
     }
//--- Declare the array for storing blur weight ratios and,
//--- if failed to get the array of weight ratios, return 'false'
   double weights[];
   if(!this.GetQuadratureWeights(1,n_nodes,weights))
      return false;
      
//--- Set components of each image pixel to the color component arrays
   for(int i=0;i<size;i++)
     {
      a_h_data[i]=GETRGBA(res_data[i]);
      r_h_data[i]=GETRGBR(res_data[i]);
      g_h_data[i]=GETRGBG(res_data[i]);
      b_h_data[i]=GETRGBB(res_data[i]);
     }

//--- Blur the image horizontally (along the X axis)
   uint XY; // Pixel coordinate in the array
   double a_temp=0.0,r_temp=0.0,g_temp=0.0,b_temp=0.0;
   int coef=0;
   int j=(int)radius;
   //--- Loop by the image width
   for(uint Y=0;Y<res_h;Y++)
     {
      //--- Loop by the image height
      for(uint X=radius;X<res_w-radius;X++)
        {
         XY=Y*res_w+X;
         a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0;
         coef=0;
         //--- Multiply each color component by the weight ratio corresponding to the current image pixel
         for(int i=-1*j;i<j+1;i=i+1)
           {
            a_temp+=a_h_data[XY+i]*weights[coef];
            r_temp+=r_h_data[XY+i]*weights[coef];
            g_temp+=g_h_data[XY+i]*weights[coef];
            b_temp+=b_h_data[XY+i]*weights[coef];
            coef++;
           }
         //--- Save each rounded color component calculated according to the ratios to the component arrays
         a_h_data[XY]=(uchar)::round(a_temp);
         r_h_data[XY]=(uchar)::round(r_temp);
         g_h_data[XY]=(uchar)::round(g_temp);
         b_h_data[XY]=(uchar)::round(b_temp);
        }
      //--- Remove blur artifacts to the left by copying adjacent pixels
      for(uint x=0;x<radius;x++)
        {
         XY=Y*res_w+x;
         a_h_data[XY]=a_h_data[Y*res_w+radius];
         r_h_data[XY]=r_h_data[Y*res_w+radius];
         g_h_data[XY]=g_h_data[Y*res_w+radius];
         b_h_data[XY]=b_h_data[Y*res_w+radius];
        }
      //--- Remove blur artifacts to the right by copying adjacent pixels
      for(uint x=res_w-radius;x<res_w;x++)
        {
         XY=Y*res_w+x;
         a_h_data[XY]=a_h_data[(Y+1)*res_w-radius-1];
         r_h_data[XY]=r_h_data[(Y+1)*res_w-radius-1];
         g_h_data[XY]=g_h_data[(Y+1)*res_w-radius-1];
         b_h_data[XY]=b_h_data[(Y+1)*res_w-radius-1];
        }
     }

//--- Blur vertically (along the Y axis) the image already blurred horizontally
   int dxdy=0;
   //--- Loop by the image height
   for(uint X=0;X<res_w;X++)
     {
      //--- Loop by the image width
      for(uint Y=radius;Y<res_h-radius;Y++)
        {
         XY=Y*res_w+X;
         a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0;
         coef=0;
         //--- Multiply each color component by the weight ratio corresponding to the current image pixel
         for(int i=-1*j;i<j+1;i=i+1)
           {
            dxdy=i*(int)res_w;
            a_temp+=a_h_data[XY+dxdy]*weights[coef];
            r_temp+=r_h_data[XY+dxdy]*weights[coef];
            g_temp+=g_h_data[XY+dxdy]*weights[coef];
            b_temp+=b_h_data[XY+dxdy]*weights[coef];
            coef++;
           }
         //--- Save each rounded color component calculated according to the ratios to the component arrays
         a_v_data[XY]=(uchar)::round(a_temp);
         r_v_data[XY]=(uchar)::round(r_temp);
         g_v_data[XY]=(uchar)::round(g_temp);
         b_v_data[XY]=(uchar)::round(b_temp);
        }
      //--- Remove blur artifacts at the top by copying adjacent pixels
      for(uint y=0;y<radius;y++)
        {
         XY=y*res_w+X;
         a_v_data[XY]=a_v_data[X+radius*res_w];
         r_v_data[XY]=r_v_data[X+radius*res_w];
         g_v_data[XY]=g_v_data[X+radius*res_w];
         b_v_data[XY]=b_v_data[X+radius*res_w];
        }
      //--- Remove blur artifacts at the bottom by copying adjacent pixels
      for(uint y=res_h-radius;y<res_h;y++)
        {
         XY=y*res_w+X;
         a_v_data[XY]=a_v_data[X+(res_h-1-radius)*res_w];
         r_v_data[XY]=r_v_data[X+(res_h-1-radius)*res_w];
         g_v_data[XY]=g_v_data[X+(res_h-1-radius)*res_w];
         b_v_data[XY]=b_v_data[X+(res_h-1-radius)*res_w];
        }
     }
     
//--- Set the twice blurred (horizontally and vertically) image pixels to the graphical resource data array
   for(int i=0;i<size;i++)
      res_data[i]=ARGB(a_v_data[i],r_v_data[i],g_v_data[i],b_v_data[i]);
//--- Display the image pixels on the canvas in a loop by the image height and width from the graphical resource data array
   for(uint X=0;X<res_w;X++)
     {
      for(uint Y=radius;Y<res_h-radius;Y++)
        {
         XY=Y*res_w+X;
         CGCnvElement::GetCanvasObj().PixelSet(X,Y,res_data[XY]);
        }
     }
//--- Done
   return true;
  }
//+------------------------------------------------------------------+

メソッドのロジックは、コードのコメントで説明されています。詳細については、メソッドの取得元である記事をご覧ください。

以下は、重み比の配列を返すメソッドです。

//+------------------------------------------------------------------+
//| Return the array of weight ratios                                |
//| https://www.mql5.com/ja/articles/1612#chapter3_2                 |
//+------------------------------------------------------------------+
bool CShadowObj::GetQuadratureWeights(const double mu0,const int n,double &weights[])
  {
   CAlglib alglib;
   double  alp[];
   double  bet[];
   ::ArrayResize(alp,n);
   ::ArrayResize(bet,n);
   ::ArrayInitialize(alp,1.0);
   ::ArrayInitialize(bet,1.0);
//---
   double out_x[];
   int    info=0;
   alglib.GQGenerateRec(alp,bet,mu0,n,info,out_x,weights);
   if(info!=1)
     {
      string txt=(info==-3 ?"internal eigenproblem solver hasn't converged" : info==-2 ?"Beta[i]<=0" : "incorrect N was passed");
      ::Print("Call error in CGaussQ::GQGenerateRec: ",txt);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

このメソッドは、ALGLIB数値解析ライブラリを使用してぼかし率を計算し、リンクから渡された重み配列に設定します。詳細については、次の記事セクションをご覧ください。

これで、影オブジェクトクラスの最初のバージョンの開発が完了しました。

次に、フォームオブジェクトから直接影をすばやく作成および描画する機能を実装します。

フォームオブジェクトクラスの\MQL5\Include\DoEasy\Objects\Graph\Form.mqhを開き、必要な改善を行います。

フォームオブジェクトクラスに影オブジェクトクラスを表示させるには、最近作成した影クラスのファイルをそれに含めます

//+------------------------------------------------------------------+
//|                                                         Form.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "GCnvElement.mqh"
#include "ShadowObj.mqh"
//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+

クラスのprivateセクションから、フォームの影の色を格納している変数を削除します。

   color             m_color_shadow;                           // Form shadow color

これで、影の色が影オブジェクトクラスに保存されるようになります。

その結果、フォームオブジェクトを使用すると、その中に新しいグラフィック要素オブジェクトを作成し、それらを依存オブジェクトのリストに添付できます。つまり、これらの新しく作成されたオブジェクトは、形状オブジェクトに完全に依存し、属します。フォームオブジェクトはそれらを管理できるようになります。このようなオブジェクトを作成するには、名前を作成する必要があります。名前には、フォームオブジェクト名が含まれ、最後に独自の名前が追加されている必要があります。これを実現するには、依存オブジェクトの名前を作成するメソッドをクラスのprivateセクションに追加します。

//--- Initialize the variables
   void              Initialize(void);
//--- Return the name of the dependent object
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
   
//--- Create a new graphical object

前回の記事でフォームオブジェクトを説明したときに、以前の記事でオブジェクトにそのような名前を作成することをすでに実装しました。

...名前オブジェクトから末尾を取得します(名前は、プログラム名とその作成時に割り当てられたオブジェクト名で構成されます)。作成中にオブジェクト名を取得し、メソッドに渡された名前を追加する必要があります。
たとえば、名前がProgram_name_Form01の場合、Form01部分文字列を取得し、メソッドに渡された名前を追加します。影オブジェクトを作成してShadowという名前を渡すと、オブジェクト名はForm01_ Shadowになり、作成されたオブジェクトの最終的な名前はProgram_name_Form01_Shadowになります。

これは、複数回必要になるため、別のメソッドで実行されます。

また、privateセクションで、影オブジェクトを作成するためのメソッドを宣言します

//--- Create a new graphical object
   CGCnvElement     *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int element_num,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity);
//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
   
public:

クラスのpublicセクションから、メソッドの宣言を削除します

//--- Create a new attached element
   bool              CreateNewElement(const int element_num,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity);

//--- Create a shadow object
   void              CreateShadow(const uchar opacity);
//--- Draw an object shadow

このメソッドは公開されておらず、影の色とその不透明度が追加で転送されます。

オブジェクトの影を描画するpublicメソッドにも、次の引数が追加されます。

//--- Create a new attached element
   bool              CreateNewElement(const int element_num,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity);



//--- Draw an object shadow
   void              DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4);

//--- Draw the form frame

これは、影オブジェクトを事前に作成する代わりに、レンダリング後にすぐに影描画メソッドを呼び出すことができるようにするためです。ここでのロジックは単純です。影描画メソッドを呼び出す場合は必要ありません。影オブジェクトを作成していない場合、新しいメソッドはまずオブジェクトを作成してその上に影を描画して、画面に表示します。

メソッドのブロックから影の色を設定および返すためのメソッドの実装を削除 して、オブジェクトのプロパティへのアクセスを簡素化します。

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) get the form frame color
   void              SetColorFrame(const color colour)                        { this.m_color_frame=colour;  }
   color             ColorFrame(void)                                   const { return this.m_color_frame;  }
//--- (1) Set and (2) return the form shadow color
   void              SetColorShadow(const color colour)                       { this.m_color_shadow=colour; }
   color             ColorShadow(void)                                  const { return this.m_color_shadow; }

これで、これらのメソッドはクラス本体の外に移動され(そこで影オブジェクトの存在を確認する必要があります)が、ここには宣言のみが残ります。また、影の不透明度を設定して返すメソッドの宣言を追加します。

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) get the form frame color
   void              SetColorFrame(const color colour)                        { this.m_color_frame=colour;     }
   color             ColorFrame(void)                                   const { return this.m_color_frame;     }
//--- (1) Set and (2) return the form shadow color
   void              SetColorShadow(const color colour);
   color             ColorShadow(void) const;
//--- (1) Set and (2) return the form shadow opacity
   void              SetOpacityShadow(const uchar opacity);
   uchar             OpacityShadow(void) const;

  };
//+------------------------------------------------------------------+

新しいグラフィック要素を作成するメソッドで次の文字列を

   int pos=::StringLen(::MQLInfoString(MQL_PROGRAM_NAME));
   string pref=::StringSubstr(NameObj(),pos+1);
   string name=pref+"_"+obj_name;

依存オブジェクト名を作成するメソッドの呼び出しで置き換えます

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int obj_num,
                                      const string obj_name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

影オブジェクトを作成したら、デフォルトのパラメータをすぐに設定する必要があります

これを実現するために、 影オブジェクトを作成するメソッドを少し改善しましょう

//+------------------------------------------------------------------+
//| Create the shadow object                                         |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- If the shadow flag is disabled or the shadow object already exists, exit
   if(!this.m_shadow || this.m_shadow_obj!=NULL)
      return;
//--- Calculate the shadow object coordinates according to the offset from the top and left
   int x=this.CoordX()-OUTER_AREA_SIZE;
   int y=this.CoordY()-OUTER_AREA_SIZE;
//--- Calculate the width and height in accordance with the top, bottom, left and right offsets
   int w=this.Width()+OUTER_AREA_SIZE*2;
   int h=this.Height()+OUTER_AREA_SIZE*2;
//--- Create a new shadow object and set the pointer to it in the variable
   this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h);
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
      return;
     }
//--- Set the properties for the created shadow object
   this.m_shadow_obj.SetID(this.ID());
   this.m_shadow_obj.SetNumber(-1);
   this.m_shadow_obj.SetOpacityShadow(opacity);
   this.m_shadow_obj.SetColorShadow(colour);
   this.m_shadow_obj.SetMovable(true);
   this.m_shadow_obj.SetActive(false);
   this.m_shadow_obj.SetVisible(false);
//--- Move the form object to the foreground
   this.BringToTop();
  }
//+------------------------------------------------------------------+

影を描画するメソッドを改善して、オブジェクトがない状態では最初に作成され、後で影が描画されるようにします。

//+------------------------------------------------------------------+
//| Draw the shadow                                                  |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4)
  {
//--- If the shadow flag is disabled, exit
   if(!this.m_shadow)
      return;
//--- If there is no shadow object, create it
   if(this.m_shadow_obj==NULL)
      this.CreateShadowObj(colour,opacity);
//--- If the shadow object exists, draw the shadow on it,
//--- set the shadow object visibility flag and
//--- move the form object to the foreground
   if(this.m_shadow_obj!=NULL)
     {
      this.m_shadow_obj.DrawShadow(shift_x,shift_y,blur);
      this.m_shadow_obj.SetVisible(true);
      this.BringToTop();
     }
  }
//+------------------------------------------------------------------+

メソッドロジックはコードコメントで説明されており、問題ないはずです。

配色を設定するメソッドでは、影オブジェクトの描画色を設定する前に、影を使用するフラグと作成された影オブジェクトの存在を確認します

//+------------------------------------------------------------------+
//| Set a color scheme                                               |
//+------------------------------------------------------------------+
void CForm::SetColorTheme(const ENUM_COLOR_THEMES theme,const uchar opacity)
  {
   this.SetOpacity(opacity);
   this.SetColorBackground(array_color_themes[theme][COLOR_THEME_COLOR_FORM_BG]);
   this.SetColorFrame(array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME]);
   if(this.m_shadow && this.m_shadow_obj!=NULL)
      this.SetColorShadow(array_color_themes[theme][COLOR_THEME_COLOR_FORM_SHADOW]);
  }
//+------------------------------------------------------------------+

フォームスタイルを設定するメソッドは、影カラーを作成するためにチャートの背景色を使用する必要があることを示す新しい入力を受け取り影の描画を追加します

//+------------------------------------------------------------------+
//| Set the form style                                               |
//+------------------------------------------------------------------+
void CForm::SetFormStyle(const ENUM_FORM_STYLE style,
                         const ENUM_COLOR_THEMES theme,
                         const uchar opacity,
                         const bool shadow=false,
                         const bool use_bg_color=true,
                         const bool redraw=false)
  {
//--- Set opacity parameters and the size of the form frame side
   this.m_shadow=shadow;
   this.m_frame_width_top=array_form_style[style][FORM_STYLE_FRAME_WIDTH_TOP];
   this.m_frame_width_bottom=array_form_style[style][FORM_STYLE_FRAME_WIDTH_BOTTOM];
   this.m_frame_width_left=array_form_style[style][FORM_STYLE_FRAME_WIDTH_LEFT];
   this.m_frame_width_right=array_form_style[style][FORM_STYLE_FRAME_WIDTH_RIGHT];
   
//--- Create the shadow object
   this.CreateShadowObj(clrNONE,(uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]);
   
//--- Set a color scheme
   this.SetColorTheme(theme,opacity);
//--- Calculate a shadow color with color darkening
   color clr=array_color_themes[theme][COLOR_THEME_COLOR_FORM_SHADOW];
   color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100);
   color color_shadow=CGCnvElement::ChangeColorLightness((use_bg_color ? gray : clr),-fabs(array_form_style[style][FORM_STYLE_DARKENING_COLOR_FOR_SHADOW]));
   this.SetColorShadow(color_shadow);
   
//--- Draw a rectangular shadow
   int shift_x=array_form_style[style][FORM_STYLE_FRAME_SHADOW_X_SHIFT];
   int shift_y=array_form_style[style][FORM_STYLE_FRAME_SHADOW_Y_SHIFT];
   this.DrawShadow(shift_x,shift_y,color_shadow,this.OpacityShadow(),(uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_BLUR]);
   
//--- Fill in the form background with color and opacity
   this.Erase(this.ColorBackground(),this.Opacity());
//--- Depending on the selected form style, draw the corresponding form frame and the outer bounding frame
   switch(style)
     {
      case FORM_STYLE_BEVEL   :
        this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_BEVEL);

        break;
      //---FORM_STYLE_FLAT
      default:
        this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_FLAT);

        break;
     }
   this.DrawRectangle(0,0,Width()-1,Height()-1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_RECT_OUTER],this.Opacity());
  }
//+------------------------------------------------------------------+

メソッドロジックはコメントで説明されています。つまり、まず影オブジェクトを作成します。カラーパレットを設定した後、影を描くために必要な色を計算します。背景色使用フラグが設定されている場合、グラフの背景色をモノクロに変換し、GraphINI.mqhファイルのフォームスタイルでパラメータ値を暗くするように設定して暗くします。フラグが設定されていない場合は、同じように暗くした色を使用します。色は、GraphINI.mqhファイルのフォームの配色で設定されます。次に、影を描画するメソッドを呼び出します。このメソッドは、フォームオブジェクトの影使用フラグが設定されている場合にのみ影を描画します。

フォームフレームの明暗を適用するすべてのメソッドで、実数で指定された値

      //--- Darken the horizontal sides of the frame
      for(int i=0;i<width;i++)
        {
         this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.05));
         this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.07));
        }

を100倍大きい適切な整数値(これらの文字列で呼び出されるメソッドでは、渡された値を100での除算を追加しました)で<s1>置き換えます</s1> 。

      //--- Darken the horizontal sides of the frame
      for(int i=0;i<width;i++)
        {
         this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-5));
         this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-7));
        }

これは、値の置換が必要なすべてのメソッドですでに実行されています。ここでは同じタイプの変更を繰り返しません。コードは以下に添付されているファイルにあります。

以下は、フォームの影の色を設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the form shadow color                                        |
//+------------------------------------------------------------------+
void CForm::SetColorShadow(const color colour)
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return;
     }
   this.m_shadow_obj.SetColorShadow(colour);
  }
//+------------------------------------------------------------------+

ここでは、影オブジェクトの存在を確認し、オブジェクトが存在する場合にのみその影の色を設定します。それ以外の場合は、影オブジェクトが存在しないことを通知し、最初に作成するように求める操作ログメッセージを表示します。

以下は、フォームの影の色を返すメソッドです。

//+------------------------------------------------------------------+
//| Return the form shadow color                                     |
//+------------------------------------------------------------------+
color CForm::ColorShadow(void) const
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return clrNONE;
     }
   return this.m_shadow_obj.ColorShadow();
  }
//+------------------------------------------------------------------+

ここでは、まずオブジェクトが存在するかどうかを確認し、そこから影の色を返します。

以下は、影の不透明度を設定して返すメソッドです。

//+------------------------------------------------------------------+
//| Set the form shadow opacity                                      |
//+------------------------------------------------------------------+
void CForm::SetOpacityShadow(const uchar opacity)
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return;
     }
   this.m_shadow_obj.SetOpacityShadow(opacity);
  }
//+------------------------------------------------------------------+
//| Return the form shadow opacity                                   |
//+------------------------------------------------------------------+
uchar CForm::OpacityShadow(void) const
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return 0;
     }
   return this.m_shadow_obj.OpacityShadow();
  }
//+------------------------------------------------------------------+

これらのメソッドのロジックは、上記で検討した2つのメソッドのロジックと同じです。

これで、フォームの影オブジェクトの作成をテストする準備が整いました。


検証

フォームオブジェクトでの影の作成を確認しましょう。2つのフォームは、フォームスタイルと配色で記述されたパラメータを使用して作成されます(前の記事で行ったのと同様)。3番目のフォームは「手動」で作成されます。これは、カスタムフォームを描画する方法のさらに別の例です。フォームの影オブジェクトはフォーム自体の作成後に描画されるため、どのオブジェクトがクリックに反応するかを確認します。フォームオブジェクトがその影が描画されるオブジェクトの上にある場合、フォームをクリックすると操作ログでフォーム名が表示されます。 影オブジェクトがまだフォームよりも上にある場合、操作ログにはフォームの影オブジェクトの名前が表示されます。

テストを実行するには、前の記事のEAを使用して、\MQL5\Experts\TestDoEasy\Part77\TestDoEasyPart77.mq5として保存します。

EA入力のリストで、チャートの背景色または次の入力で指定できる指定された色影の色を選択できるようにする設定を追加します。グローバル変数のリストで、フォームにグラデーションをつけるするための色を格納する配列を追加します

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart77.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <Arrays\ArrayObj.mqh>
#include <DoEasy\Services\Select.mqh>
#include <DoEasy\Objects\Graph\Form.mqh>
//--- defines
#define        FORMS_TOTAL (3)   // Number of created forms
//--- input parameters
sinput   bool              InpMovable     =  true;          // Movable forms flag
sinput   ENUM_INPUT_YES_NO InpUseColorBG  =  INPUT_YES;     // Use chart background color to calculate shadow color
sinput   color             InpColorForm3  =  clrCadetBlue;  // Third form shadow color (if not background color) 
//--- global variables
CArrayObj      list_forms;
color          array_clr[];
//+------------------------------------------------------------------+

OnInit()ハンドラで、 3番目のフォームオブジェクトの作成を追加します

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables
   ArrayResize(array_clr,2);
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the specified number of form objects
   list_forms.Clear();
   int total=FORMS_TOTAL;
   for(int i=0;i<total;i++)
     {
      //--- When creating an object, pass all the required parameters to it
      CForm *form=new CForm("Form_0"+(string)(i+1),300,40+(i*80),100,(i<2 ? 70 : 30));
      if(form==NULL)
         continue;
      //--- Set activity and moveability flags for the form
      form.SetActive(true);
      form.SetMovable(false);
      //--- Set the form ID equal to the loop index and the index in the list of objects
      form.SetID(i);
      form.SetNumber(0);   // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them
      //--- Set the partial opacity for the middle form and the full one for the rest
      uchar opacity=(i==1 ? 250 : 255);
      //--- Set the form style and its color theme depending on the loop index
      if(i<2)
        {
         ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i;
         ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i;
         //--- Set the form style and theme
         form.SetFormStyle(style,theme,opacity,true,false);
        }
      //--- If this is the first (top) form
      if(i==0)
        {
         //--- Draw a concave field slightly shifted from the center of the form downwards
         form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity());
         form.Update(true);
        }
      //--- If this is the second (middle) form
      if(i==1)
        {
         //--- Draw a concave semi-transparent "tainted glass" field in the center
         form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200);
         form.Update(true);
        }
      //--- If this is the third (bottom) form
      if(i==2)
        {
         //--- Set the opacity of 200
         form.SetOpacity(200);
         //--- The form background color is set as the first color from the color array
         form.SetColorBackground(array_clr[0]);
         //--- Form outlining frame color
         form.SetColorFrame(clrDarkBlue);
         //--- Draw the shadow drawing flag
         form.SetShadow(true);
         //--- Calculate the shadow color as the chart background color converted to the monochrome one
         color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
         //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units
         //--- Otherwise, use the color specified in the settings for drawing the shadow
         color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,255,-20) : InpColorForm3);
         //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
         //--- Set the shadow opacity to 200, while the blur radius is equal to 4
         form.DrawShadow(3,3,clr,200,4);
         //--- Fill the form background with a vertical gradient
         form.Erase(array_clr,form.Opacity());
         //--- Draw an outlining rectangle at the edges of the form
         form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
         //--- Display the text describing the gradient type and update the form
         form.Text(form.Width()/2,form.Height()/2,TextByLanguage("V-Градиент","V-Gradient"),C'211,233,149',255,TEXT_ANCHOR_CENTER);
         form.Update(true);
        }
      //--- Add objects to the list
      if(!list_forms.Add(form))
        {
         delete form;
         continue;
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

フォーム作成ロジック全体は、コードコメントで設定されます。これは、カスタムフォームオブジェクトを作成するためのさらに別のオプションです。

OnChartEvent()ハンドラで、グラフィカルオブジェクトをクリックすると、操作ログでグラフィカルオブジェクト名が表示されるようにします

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If clicking on an object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      Print(sparam);
     }
  }
//+------------------------------------------------------------------+

EAをコンパイルし、チャート上で起動して、デフォルトの影の設定を変更します。


残念ながら、GIF画像ではカラーパレット全体を表示することはできません。

以下に、画像PNG形式のグラデーションの背景を持つフォームの外観を示します。


各フォームをクリックすると、(影オブジェクトの名前ではなく)フォームの名前が操作ログに表示されます。

TestDoEasyPart77_Form_01
TestDoEasyPart77_Form_02
TestDoEasyPart77_Form_03

これは、影オブジェクトをフォームオブジェクトから作成した後でも、それを作成したフォームとの干渉を回避するために、影オブジェクトを背景に移動できることを意味します。

次の段階

次の記事では、フォームオブジェクトクラスの開発を続け、徐々に静止画像の「アニメーション化」を開始します。

ライブラリの現在のバージョンのすべてのファイルは、テストおよびダウンロードできるように、MQL5のテストEAファイルと一緒に以下に添付されています。
質問や提案はコメント欄にお願いします。

目次に戻る

**連載のこれまでの記事:

DoEasyライブラリのグラフィックス(第73部): グラフィック要素のフォームオブジェクト
DoEasyライブラリのグラフィックス(第74部): CCanvasクラスを使用した基本的グラフィック要素
DoEasyライブラリのグラフィックス(第75部): 基本的なグラフィック要素でプリミティブとテキストを処理するメソッド
DoEasyライブラリのグラフィックス(第76部): フォームオブジェクトと事前定義されたカラースキーム

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/9575

添付されたファイル |
MQL5.zip (3986.77 KB)
取引のための組合せ論と確率論(第II部): ユニバーサルフラクタル 取引のための組合せ論と確率論(第II部): ユニバーサルフラクタル
本稿では、フラクタルの研究を続け、すべての資料の要約に特に注意を払います。これを行うために、これまでの開発をすべて、取引での実用化に便利で理解しやすいコンパクトな形にまとめてみます。
DoEasyライブラリのグラフィックス(第76部): フォームオブジェクトと事前定義されたカラースキーム DoEasyライブラリのグラフィックス(第76部): フォームオブジェクトと事前定義されたカラースキーム
本稿では、さまざまなライブラリGUIデザインテーマの構築の概念について説明し、グラフィック要素クラスオブジェクトの子孫であるフォームオブジェクトを作成し、ライブラリのグラフィカルオブジェクトのシャドウを作成するため、および機能をさらに開発するためのデータを準備します。
DoEasyライブラリのグラフィックス(第78部): ライブラリのアニメーションの原則イメージスライス DoEasyライブラリのグラフィックス(第78部): ライブラリのアニメーションの原則イメージスライス
この記事では、ライブラリの一部で使用されるアニメーションの原則を定義します。また、画像の一部をコピーして指定したフォームオブジェクトの場所に貼り付け、画像が重ねられるフォームの背景の一部を保存して復元するクラスを開発します。
クラスター分析(第I部):インジケーターラインの傾きをマスターする クラスター分析(第I部):インジケーターラインの傾きをマスターする
クラスター分析は、人工知能の最も重要な要素の1つです。この記事では、指標の傾きのクラスター分析を適用して、市場が横ばいであるかトレンドに従っているのかを判断するためのしきい値の取得を試みます。