English Русский 中文 Español Deutsch Português
DoEasyライブラリのグラフィックス(第78部): ライブラリのアニメーションの原則イメージスライス

DoEasyライブラリのグラフィックス(第78部): ライブラリのアニメーションの原則イメージスライス

MetaTrader 5 | 6 10月 2021, 15:50
226 0
Artyom Trishkin
Artyom Trishkin

内容


概念

グラフィカルインターフェイスは本質的に非静的画像の存在を意味します。表示されるデータ(たとえば、表に示されているデータ)は、時間の経過とともに変化する可能性があります。GUI要素は、さまざまな視覚効果などを使用して、ユーザの操作に反応する場合があります。

さまざまな視覚効果メソッドを作成し、ライブラリにスプライトアニメーションを操作する機能を与えます。アニメーションは、静止画像の変化する(フレームごとの)シーケンスの使用に基づいています。
CCanvasクラスを使用すると、キャンバスに画像を描画できます。描画され画像配列に保存された一連の画像から、特定のシーケンスを作成すると、これは最終的にアニメーション画像になります。ただし、後続の各画像を1つずつキャンバスに描画したとすれば、それらは単に互いに重なり合い、最終的には下の画像のように、混沌としたピクセルの山になってしまいます(ここでは、フォームオブジェクトのさまざまな場所にテキストを表示しています)。


これを回避するには、前の画像を完全に消去し、背景を再描画してその上にテキストを表示する必要があります(これは、テキストをフォームに配置してテキストアンカーメソッドを説明するときに、前の記事の1つで行いました)。このオプションは、再描画されたフォームのサイズが小さく複雑でない場合にのみ実行可能です。もう1つのオプションは、テキストを重ね合わせる背景の一部を(配列内の)メモリに保存してから、テキストを追加することです。新しい座標に再配置する場合は、配列から以前に保存した背景画像を使用して描画したテキストを上書きし(背景を復元するため)、新しい場所にテキストを描画します(テキストの移動先の背景の一部を事前に保存します) 。したがって、画像を重ね合わせる場所の背景は常にメモリに保存され、画像を変更する必要がある場合は復元されます。

これは、ライブラリで紹介するスプライトアニメーションの概念の最小要素です。

  1. 必要な座標で背景を保存する
  2. 座標を使用して画像を表示する
  3. 画像を再描画するときに背景を復元する

これをすべて実現するために、画像の座標とサイズを格納するための小さなクラスを作成します。これらの座標とサイズを使用して背景画像の一部を保存するメソッドは、クラスでも作成されます。さらに、配列に保存された背景を格納する2番目のメソッドが必要になります(サイズと座標は、背景を配列に保存するときにクラス変数に保存されます)。

フォームオブジェクトに対してそのようなメソッドを2つ作成するのではなく、クラスを作成するのはなぜでしょうか。ここではすべてが簡単です。テキストまたは単一のアニメーション画像のみを表示する必要がある場合は2つのメソッドで十分ですが、フォームのさまざまな場所に複数のテキストを表示する必要がある場合は、クラスの方が便利だからです。各アニメーション画像は、個別に管理できる独自のクラスインスタンスを受け取ります。

このような概念により、以前に描画した画像を背景として使用した描画が可能になります。背景と描画した画像の両方を保存し、それらを背景から削除できます。

この概念を使用して、フォームオブジェクトにさまざまなスプライトアニメーションを作成、保存、表示するためのクラスを開発します。各クラスインスタンスには、リストに動的に追加して処理できる一連の画像が含まれています。


ライブラリクラスの改善

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

//--- 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

//--- CGCnvElement
   MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY,                  // Error! Empty array
   
//--- 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 new shadow object
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ,          // Failed to create new pixel copier object
   MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST,            // Pixel copier object with ID already present in the list 
   MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST,             // No pixel copier object with ID in the list 

//--- CShadowObj
   MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE,               // Error! Image size too small or blur too extensive
   
  };
//+------------------------------------------------------------------+

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

//--- 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"},

//--- CGCnvElement
   {"Ошибка! Пустой массив","Error! Empty array"},

//--- CForm
   {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj () method"},
   {"Не удалось создать новый объект для тени","Failed to create new object for shadow"},
   {"Не удалось создать новый объект-копировщик пикселей","Failed to create new pixel copier object"},
   {"В списке уже есть объект-копировщик пикселей с идентификатором ","There is already a pixel copier object in the list with ID "},
   {"В списке нет объекта-копировщика пикселей с идентификатором ","No pixel copier object with ID "},
   
//--- CShadowObj
   {"Ошибка! Размер изображения очень маленький или очень большое размытие","Error! Image size is very small or very large blur"},
      
  };
//+---------------------------------------------------------------------+


グラフィック要素オブジェクトから継承された既製のフォームオブジェクト、またはカスタムプログラムの他のGUIオブジェクトに画像やテキストを描画するため、いつでも元の形式に復元できるように、オブジェクトの初期外観を常に手元に置いておく必要があります。
もちろん、新しく再描画することもできますが、配列間のコピーははるかに高速です。

これを行うために、グラフィック要素オブジェクトクラスの\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhでいくつかの変更と改善を行います。

クラスのprotectedセクションで、配列を宣言します。これには、作成直後に初期オブジェクト(その外観)のすべてのピクセルが含まれます。また、CCanvasクラスインスタンスのグラフィカルリソースを配列に保存しするメソッドも宣言します。

//+------------------------------------------------------------------+
//| Class of the graphical element object                            |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CCanvas           m_canvas;                                 // CCanvas class object
   CPause            m_pause;                                  // Pause class object
   bool              m_shadow;                                 // Shadow presence
   color             m_chart_color_bg;                         // Chart background color
   uint              m_data_array[];                           // Array for storing resource data copy
//--- Return the cursor position relative to the (1) entire element and (2) the element's active area
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
//--- Save the graphical resource to the array
   bool              ResourceCopy(const string source);

private:

少量のメモリを犠牲にして配列を別の配列にコピーするだけで、プログラムインターフェイスの任意の要素の外観を元の形式にすばやく復元できます。

クラスのprivateセクションで、最後に描画されたテキストのX座標Y座標を格納するための2つの変数を宣言します。

   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];    // Integer properties
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];   // Real properties
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];   // String properties
   
   ENUM_TEXT_ANCHOR  m_text_anchor;                            // Current text alignment
   int               m_text_x;                                 // Text last X coordinate
   int               m_text_y;                                 // Text last Y coordinate
   color             m_color_bg;                               // Element background color
   uchar             m_opacity;                                // Element opacity
   
//--- Return the index of the array the order's (1) double and (2) string properties are located at


クラスのpublicセクションで、現在のクラスインスタントへのポインタを返すメソッドを記述し、指定された配列に画像を保存するメソッドを宣言します

//--- Return the flag of the object supporting this property
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property)          { return true;    }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)           { return false;   }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)           { return true;    }

//--- Return itself
   CGCnvElement     *GetObject(void)                                                   { return &this;   }

//--- Compare CGCnvElement objects with each other by all possible properties (for sorting the lists by a specified object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CGCnvElement objects with each other by all properties (to search equal objects)
   bool              IsEqual(CGCnvElement* compared_obj) const;

//--- (1) Save the object to file and (2) upload the object from the file
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   
//--- Create the element
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const color colour,
                            const uchar opacity,
                            const bool redraw=false);
                                
//--- Return the pointer to a canvas object
   CCanvas          *GetCanvasObj(void)                                                { return &this.m_canvas;                        }
//--- Set the canvas update frequency
   void              SetFrequency(const ulong value)                                   { this.m_pause.SetWaitingMSC(value);            }
//--- Update the coordinates (shift the canvas)
   bool              Move(const int x,const int y,const bool redraw=false);

//--- Save an image to the array
   bool              ImageCopy(const string source,uint &array[]);
   

クラスが自らへのポインタを戻すためのメソッドは、クラスへのポインタを以下で検討するピクセルコピークラスに渡すために必要です。一方、CCanvasインスタンスのグラフィカルリソースをコピーするメソッドは、フォームの外観をライブラリベースのプログラムの必要な配列にすばやくコピーするために必要です。

テキストを操作するメソッドのコードブロックに、最後に描画されたテキストのX座標Y座標を返す2つのメソッドを追加します。

//+------------------------------------------------------------------+
//| Methods of working with text                                     |
//+------------------------------------------------------------------+
//--- Return (1) alignment type (anchor method), the last (2) X and (3) Y text coordinate
   ENUM_TEXT_ANCHOR  TextAnchor(void)                       const { return this.m_text_anchor;                                         }
   int               TextLastX(void)                        const { return this.m_text_x;                                              }
   int               TextLastY(void)                        const { return this.m_text_y;                                              }
//--- Set the current font

メソッドは、適切な変数の値を返すだけです。

これらの値が常に関連性を保つために、現在のフォントを使用してテキストを表示するメソッドの変数に、メソッド引数に渡された座標書き込みます。

//--- Display the text in the current font
   void              Text(int         x,                          // X coordinate of the text anchor point
                          int         y,                          // Y coordinate of the text anchor point
                          string      text,                       // Display text
                          const color clr,                        // Color
                          const uchar opacity=255,                // Opacity
                          uint        alignment=0)                // Text anchoring method
                       { 
                        this.m_text_anchor=(ENUM_TEXT_ANCHOR)alignment;
                        this.m_text_x=x;
                        this.m_text_y=y;
                        this.m_canvas.TextOut(x,y,text,::ColorToARGB(clr,opacity),alignment);
                       }


描画されたテキストには、9つのアンカーポイントを含めることができます。


たとえば、テキストアンカーポイントが右下隅(Right|Bottom)にある場合、これが開始XY座標になります。ライブラリ内のすべての初期座標は、長方形の左上隅(Left|Top)に対応するため、初期テキスト座標を使用して画像を保存すると、テキストは保存された画像の右下に配置されます。これでは、テキストが重ねられる背景の領域を正しく保存できません。

したがって、長方形の輪郭を描くテキストの座標のオフセットを計算し、その後の復元のために、背景を配列に保存する必要があります。将来のテキストの幅と高さは、テキストを描画する前に事前に計算されるため、指定する必要があるのはテキスト自体だけです。CCanvasクラスのTextSize()メソッドは、輪郭を描く長方形の幅と高さを返します。

クラスのpublicセクションで、テキスト配置メソッドに応じてX/Yオフセットを返すメソッドを宣言します。

//--- Return coordinate offsets relative to the text anchor point
   void              TextGetShiftXY(const string text,            // Text for calculating the size of its outlining rectangle
                                    const ENUM_TEXT_ANCHOR anchor,// Text anchor point, relative to which the offsets are calculated
                                    int &shift_x,                 // X coordinate of the rectangle upper left corner
                                    int &shift_y);                // Y coordinate of the rectangle upper left corner

  };
//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+

このメソッドについては、以下で検討します。

パラメトリッククラスコンストラクタで最後に描画されたテキストの座標を初期化します

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_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=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.m_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND);
   this.m_name=this.m_name_prefix+name;
   this.m_chart_id=chart_id;
   this.m_subwindow=wnd_num;
   this.m_type=element_type;
   this.SetFont("Calibri",8);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_color_bg=colour;
   this.m_opacity=opacity;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID
      this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow());        // Chart subwindow index
      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Element object name
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Graphical element type
      this.SetProperty(CANV_ELEMENT_PROP_ID,element_id);                         // Element ID
      this.SetProperty(CANV_ELEMENT_PROP_NUM,element_num);                       // Element index in the list
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,x);                             // Element's X coordinate on the chart
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,y);                             // Element's Y coordinate on the chart
      this.SetProperty(CANV_ELEMENT_PROP_WIDTH,w);                               // Element width
      this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,h);                              // Element height
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,0);                      // Active area offset from the left edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,0);                       // Active area offset from the upper edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,0);                     // Active area offset from the right edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0);                    // Active area offset from the bottom edge of the element
      this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,movable);                       // Element moveability flag
      this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity);                       // Element activity flag
      this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());                // Element right border
      this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());              // Element bottom border
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.ActiveAreaLeft());     // X coordinate of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.ActiveAreaTop());      // Y coordinate of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight());      // Right border of the element active area
      this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom());    // Bottom border of the element active area
     }
   else
     {
      ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.m_name);
     }
  }
//+------------------------------------------------------------------+

同様に、protectedコンストラクタで変数を初期化します

//+------------------------------------------------------------------+
//| Protected constructor                                            |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  name,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND);
   this.m_name=this.m_name_prefix+name;
   this.m_chart_id=chart_id;
   this.m_subwindow=wnd_num;
   this.m_type=element_type;
   this.SetFont("Calibri",8);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_color_bg=NULL_COLOR;
   this.m_opacity=0;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,this.m_color_bg,this.m_opacity,false))
     {
      ...

次に、上記で宣言されたメソッドの実装について考えてみましょう。

画像を配列に保存するメソッドを実装します。

//+------------------------------------------------------------------+
//| Save the image to the array                                      |
//+------------------------------------------------------------------+
bool CGCnvElement::ImageCopy(const string source,uint &array[])
  {
   ::ResetLastError();
   int w=0,h=0;
   if(!::ResourceReadImage(this.NameRes(),array,w,h))
     {
      CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_DATA_GRAPH_RES,true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

メソッドは、(エラーの可能性を見つけるために)呼び出されたメソッドまたは関数の名前とグラフィカルリソースデータ(画像ピクセル)が書き込まれる配列へのリンクを受け取ります。

ResourceReadImage()関数を使用して、CCanvasクラスによって作成され、フォームの画像を含むグラフィカルリソースのデータを配列に読み込みます。リソース読み取りエラーが発生した場合は、そのことを通知し、falseを返します。すべて問題がなければ、trueを返します。 リソースに保存されているすべての画像ピクセルは、メソッドに渡された配列に書き込まれます。

以下は、グラフィカルリソースを配列に保存するメソッドです。

//+------------------------------------------------------------------+
//| Save the graphical resource to the array                         |
//+------------------------------------------------------------------+
bool CGCnvElement::ResourceCopy(const string source)
  {
   return this.ImageCopy(DFUN,this.m_data_array);
  }
//+------------------------------------------------------------------+

このメソッドは、上記で検討したメソッドを呼び出した結果を返します。唯一の違いは、グラフィカルリソースデータが、リンクによって渡される配列ではなく、フォームオブジェクト全体の画像のコピーを格納するために以前に宣言された特別な配列に書き込まれることです。

以下は、テキストアンカーポイントを基準にした座標オフセットを返すメソッドです。

//+------------------------------------------------------------------+
//| Return coordinate offsets relative to the text anchor point      |
//+------------------------------------------------------------------+
void CGCnvElement::TextGetShiftXY(const string text,const ENUM_TEXT_ANCHOR anchor,int &shift_x,int &shift_y)
  {
   int tw=0,th=0;
   this.TextSize(text,tw,th);
   switch(anchor)
     {
      case TEXT_ANCHOR_LEFT_TOP :
        shift_x=0; shift_y=0;
        break;
      case TEXT_ANCHOR_LEFT_CENTER :
        shift_x=0; shift_y=-th/2;
        break;
      case TEXT_ANCHOR_LEFT_BOTTOM :
        shift_x=0; shift_y=-th;
        break;
      case TEXT_ANCHOR_CENTER_TOP :
        shift_x=-tw/2; shift_y=0;
        break;
      case TEXT_ANCHOR_CENTER :
        shift_x=-tw/2; shift_y=-th/2;
        break;
      case TEXT_ANCHOR_CENTER_BOTTOM :
        shift_x=-tw/2; shift_y=-th;
        break;
      case TEXT_ANCHOR_RIGHT_TOP :
        shift_x=-tw; shift_y=0;
        break;
      case TEXT_ANCHOR_RIGHT_CENTER :
        shift_x=-tw; shift_y=-th/2;
        break;
      case TEXT_ANCHOR_RIGHT_BOTTOM :
        shift_x=-tw; shift_y=-th;
        break;
      default:
        shift_x=0; shift_y=0;
        break;
     }
  }
//+------------------------------------------------------------------+

ここでは、まずメソッドに渡されるテキストのサイズを取得し(サイズは宣言された変数で設定されます)、次に、メソッドに渡されるテキストのアンカーメソッドに応じて初期テキスト座標に依存して、XおよびY座標を初期のテキスト座標に対して移動するために必要なピクセル数を計算します。

次に影オブジェクトクラスを改善します。グラフィカルリソースを読み取るためのメソッドと、グラフィカルリソースのコピーを格納できる定数配列を追加したばかりなので、過剰な変数、配列、およびコードブロックを影オブジェクトクラスから削除できます。

\MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqhファイルを改善しましょう。

ガウスぼかしメソッドから配列と不要な変数を削除します。

//+------------------------------------------------------------------+
//| 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. 失敗した場合は、falseを返します

グラフィカルリソースデータを読み取るブロックで、文字列を上記のメソッドの呼び出しで置き換えます。

//--- 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'

//--- Read graphical resource data. If failed, return false
   if(!CGCnvElement::ResourceCopy(DFUN))
      return false;

コード全体で、削除されたres_w 変数とres_h 変数の代わりにグラフィック要素オブジェクトクラスのメソッドのWidth()メソッドとHeight()メソッドを使用します。res_data配列の代わりに、m_data_array配列を使用します。これは現在グラフィカルリソースのコピーを格納するために使用されています。

一般に、すべての改善は、不要で削除された変数をグラフィック要素オブジェクトクラスのメソッドに置き換えることに要約されています。

//+------------------------------------------------------------------+
//| Gaussian blur                                                    |
//| https://www.mql5.com/ja/articles/1612#chapter4                   |
//+------------------------------------------------------------------+
bool CShadowObj::GaussianBlur(const uint radius)
  {
//---
   int n_nodes=(int)radius*2+1;
//--- Read graphical resource data. If failed, return false
   if(!CGCnvElement::ResourceCopy(DFUN))
      return false;
   
//--- Check the blur amount. If the blur radius exceeds half of the width or height, return 'false'
   if((int)radius>=this.Width()/2 || (int)radius>=this.Height()/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(this.m_data_array);
//--- 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::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"a_h_data\"");
      return false;
     }
   if(::ArrayResize(r_h_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"r_h_data\"");
      return false;
     }
   if(::ArrayResize(g_h_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"g_h_data\"");
      return false;
     }
   if(ArrayResize(b_h_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"b_h_data\"");
      return false;
     }
   if(::ArrayResize(a_v_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"a_v_data\"");
      return false;
     }
   if(::ArrayResize(r_v_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"r_v_data\"");
      return false;
     }
   if(::ArrayResize(g_v_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"g_v_data\"");
      return false;
     }
   if(::ArrayResize(b_v_data,size)==-1)
     {
      CMessage::ToLog(DFUN,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(this.m_data_array[i]);
      r_h_data[i]=GETRGBR(this.m_data_array[i]);
      g_h_data[i]=GETRGBG(this.m_data_array[i]);
      b_h_data[i]=GETRGBB(this.m_data_array[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(int Y=0;Y<this.Height();Y++)
     {
      //--- Loop by the image height
      for(uint X=radius;X<this.Width()-radius;X++)
        {
         XY=Y*this.Width()+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*this.Width()+x;
         a_h_data[XY]=a_h_data[Y*this.Width()+radius];
         r_h_data[XY]=r_h_data[Y*this.Width()+radius];
         g_h_data[XY]=g_h_data[Y*this.Width()+radius];
         b_h_data[XY]=b_h_data[Y*this.Width()+radius];
        }
      //--- Remove blur artifacts to the right by copying adjacent pixels
      for(int x=int(this.Width()-radius);x<this.Width();x++)
        {
         XY=Y*this.Width()+x;
         a_h_data[XY]=a_h_data[(Y+1)*this.Width()-radius-1];
         r_h_data[XY]=r_h_data[(Y+1)*this.Width()-radius-1];
         g_h_data[XY]=g_h_data[(Y+1)*this.Width()-radius-1];
         b_h_data[XY]=b_h_data[(Y+1)*this.Width()-radius-1];
        }
     }

//--- Blur vertically (along the Y axis) the image already blurred horizontally
   int dxdy=0;
   //--- Loop by the image height
   for(int X=0;X<this.Width();X++)
     {
      //--- Loop by the image width
      for(uint Y=radius;Y<this.Height()-radius;Y++)
        {
         XY=Y*this.Width()+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)this.Width();
            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*this.Width()+X;
         a_v_data[XY]=a_v_data[X+radius*this.Width()];
         r_v_data[XY]=r_v_data[X+radius*this.Width()];
         g_v_data[XY]=g_v_data[X+radius*this.Width()];
         b_v_data[XY]=b_v_data[X+radius*this.Width()];
        }
      //--- Remove blur artifacts at the bottom by copying adjacent pixels
      for(int y=int(this.Height()-radius);y<this.Height();y++)
        {
         XY=y*this.Width()+X;
         a_v_data[XY]=a_v_data[X+(this.Height()-1-radius)*this.Width()];
         r_v_data[XY]=r_v_data[X+(this.Height()-1-radius)*this.Width()];
         g_v_data[XY]=g_v_data[X+(this.Height()-1-radius)*this.Width()];
         b_v_data[XY]=b_v_data[X+(this.Height()-1-radius)*this.Width()];
        }
     }
     
//--- Set the twice blurred (horizontally and vertically) image pixels to the graphical resource data array
   for(int i=0;i<size;i++)
      this.m_data_array[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(int X=0;X<this.Width();X++)
     {
      for(uint Y=radius;Y<this.Height()-radius;Y++)
        {
         XY=Y*this.Width()+X;
         this.m_canvas.PixelSet(X,Y,this.m_data_array[XY]);
        }
     }
//--- Done
   return true;
  }
//+------------------------------------------------------------------+

これで、クラスを開発する準備が整いました。そのオブジェクトを使用すると、キャンバス上の任意のグラフィック要素の描画を管理できるため、後で新しい描画が重ねられた画像の背景を簡単に復元できます。さらに、これにより、スプライトアニメーションを操作するためのクラスを作成できるようになります。


画像の一部をコピーして貼り付けるためのクラス

フォームオブジェクトクラスは、継承階層内の最小オブジェクトであり、アニメーションを操作できるようにする必要があります。
画像の一部を保存および復元するためのクラスは小さいため、フォームオブジェクトクラスファイル\MQL5\Include\DoEasy\Objects\Graph\Form.mqhに直接配置します。クラスに「ピクセルコピー(pixel copier)」という名前を付けます。これは、その目的を明確に説明しています。

各ピクセルコピークラスオブジェクトには、オブジェクトが処理している画像を定義できるカスタムIDが必要です。必要なクラスオブジェクトをIDで参照して、アニメーション化された各オブジェクトを個別に処理できます。たとえば、3つの画像を同時に管理および変更する必要があり、そのうちの2つがテキストで1つが画像で、画像ごとにコピーオブジェクトを作成する場合、「text1 = ID0、 text2 = ID1、image = ID2」のように異なるIDを割り当てる必要があります—。この場合、各オブジェクトは、それを操作するための残りのすべてのパラメータを格納します。

  • 画像が重ね合わされる背景の一部を格納するピクセルの配列
  • 画像が重ね合わされる背景の長方形領域の左上隅のX座標とY座標
  • 長方形の領域の幅と高さ
  • 計算された領域の幅と高さ

長方形がピクセルを保存する必要のあるフォームの領域を超えた場合に、長方形のコピー領域の幅と高さを正確に知るために、計算された幅と高さが必要です。さらに、背景を復元するには、実際にコピーされた長方形の背景領域の幅と高さを再計算する必要はなくなり、オブジェクト変数に格納されている計算済みの値を使用するだけです。

クラスのprivateセクションで、グラフィック要素オブジェクトクラスへのポインタ(これを新しく作成したピクセルコピークラスオブジェクトに渡して、フォームのデータを使用できるようにします。このフォームで、コピーオブジェクトのインスタンスを作成します)、保存および復元する必要があるフォーム画像の一部を格納する配列、および上記のすべての変数を宣言します。

//+------------------------------------------------------------------+
//|                                                         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"
//+------------------------------------------------------------------+
//| Pixel copier class                                               |
//+------------------------------------------------------------------+
class CPixelCopier : public CObject
  {
private:
   CGCnvElement     *m_element;                             // Pointer to the graphical element
   uint              m_array[];                             // Pixel array
   int               m_id;                                  // ID
   int               m_x;                                   // X coordinate of the upper left corner
   int               m_y;                                   // Y coordinate of the upper left corner
   int               m_w;                                   // Copied image width
   int               m_h;                                   // Copied image height
   int               m_wr;                                  // Calculated copied image width
   int               m_hr;                                  // Calculated copied image height
public:

クラスのpublicセクションに、 2つのコピーオブジェクトを比較するメソッド、オブジェクトプロパティを設定するメソッド受け取るメソッドデフォルトクラスコンストラクタパラメトリックコンストラクタ背景部分を保存および復元するための2つのメソッドを宣言します。

public:
//--- Compare CPixelCopier objects by a specified property (to sort the list by an object property)
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CPixelCopier *obj_compared=node;
                        return(mode==0 ? (this.ID()>obj_compared.ID() ? 1 : this.ID()<obj_compared.ID() ? -1 : 0) : WRONG_VALUE);
                       }
   
//--- Set the properties
   void              SetID(const int id)                       { this.m_id=id;      }
   void              SetCoordX(const int value)                { this.m_x=value;    }
   void              SetCoordY(const int value)                { this.m_y=value;    }
   void              SetWidth(const int value)                 { this.m_w=value;    }
   void              SetHeight(const int value)                { this.m_h=value;    }
//--- Get the properties
   int               ID(void)                            const { return this.m_id;  }
   int               CoordX(void)                        const { return this.m_x;   }
   int               CoordY(void)                        const { return this.m_y;   }
   int               Width(void)                         const { return this.m_w;   }
   int               Height(void)                        const { return this.m_h;   }
   int               WidthReal(void)                     const { return this.m_wr;  }
   int               HeightReal(void)                    const { return this.m_hr;  }

//--- Copy the part or the entire image to the array
   bool              CopyImgDataToArray(const uint x_coord,const uint y_coord,uint width,uint height);
//--- Copy the part or the entire image from the array to the canvas
   bool              CopyImgDataToCanvas(const int x_coord,const int y_coord);

//--- Constructors
                     CPixelCopier (void){;}
                     CPixelCopier (const int id,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h,
                                   CGCnvElement *element) : m_id(id), m_x(x),m_y(y),m_w(w),m_wr(w),m_h(h),m_hr(h) { this.m_element=element; }
                    ~CPixelCopier (void){;}
  };
//+------------------------------------------------------------------+

メソッドについて詳しく見ていきましょう。

以下は、2つのコピーオブジェクトを比較するメソッドです。

//--- Compare CPixelCopier objects by a specified property (to sort the list by an object property)
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CPixelCopier *obj_compared=node;
                        return(mode==0 ? (this.ID()>obj_compared.ID() ? 1 : this.ID()<obj_compared.ID() ? -1 : 0) : WRONG_VALUE);
                       }

ここでは、他のライブラリクラスと同様に、すべてが標準です。比較モード(mode)が0(デフォルト)に等しい場合、現在のオブジェクトのIDと、メソッドに渡されるポインターであるオブジェクトのIDが比較されます。現在のオブジェクトIDがより大きい場合は1、より小さい場合は-1、等しい場合は-0が返されます。他のすべての場合(mode != 0の場合)、-1が返されます。現在、このメソッドはオブジェクトIDのみを比較できます。

パラメトリッククラスコンストラクタの初期化リストでは、引数で渡された値はすべてのクラスメンバ変数に割り当てられますが、クラス本体ではポインタ値はグラフィック要素オブジェクトクラスを指す変数に割り当てられます。 ポインタ値はまた引数で渡されます

CPixelCopier (const int id,
              const int x,
              const int y,
              const int w,
              const int h,
              CGCnvElement *element) : m_id(id), m_x(x),m_y(y),m_w(w),m_wr(w),m_h(h),m_hr(h) { this.m_element=element; }

これで、新しく作成されたコピーオブジェクトは、どのオブジェクトがそれを作成したかを「認識」し、そのメソッドとパラメータにアクセスできるようになります。

以下は、画像の一部または全部を配列にコピーするメソッドです。

//+------------------------------------------------------------------+
//| Copy part or all of the image to the array                       |
//+------------------------------------------------------------------+
bool CPixelCopier::CopyImgDataToArray(const uint x_coord,const uint y_coord,uint width,uint height)
  {
//--- Assign coordinate values, passed to the method, to the variables
   int x1=(int)x_coord;
   int y1=(int)y_coord;
//--- If X coordinates goes beyond the form on the right or Y coordinate goes beyond the form at the bottom,
//--- there is nothing to copy, the copied area is outside the form. Return 'false'
   if(x1>this.m_element.Width()-1 || y1>this.m_element.Height()-1)
      return false;
//--- Assign the width and height values of the copied area to the variables
//--- If the passed width and height are equal to zero, assign the form width and height to them
   this.m_wr=int(width==0  ? this.m_element.Width()  : width);
   this.m_hr=int(height==0 ? this.m_element.Height() : height);
//--- If X and Y coordinates are equal to zero (the upper left corner of the form), as well as the width and height are equal to the form width and height,
//--- the copied area is equal to the entire form area. Copy the entire form (returning it from the method) using the ImageCopy() method
   if(x1==0 && y1==0 && this.m_wr==this.m_element.Width() && this.m_hr==this.m_element.Height())
      return this.m_element.ImageCopy(DFUN,this.m_array);

//--- Calculate the right X coordinate and lower Y coordinate of the rectangle area
   int x2=int(x1+this.m_wr-1);
   int y2=int(y1+this.m_hr-1);
//--- If the calculated X coordinate goes beyond the form, the right edge of the form will be used as the coordinate
   if(x2>=this.m_element.Width()-1)
      x2=this.m_element.Width()-1;
//--- If the calculated Y coordinate goes beyond the form, the bottom edge of the form will be used as the coordinate
   if(y2>=this.m_element.Height()-1)
      y2=this.m_element.Height()-1;
//--- Calculate the copied width and height
   this.m_wr=x2-x1+1;
   this.m_hr=y2-y1+1;
//--- Define the necessary size of the array, which is to store all image pixels with calculated width and height
   int size=this.m_wr*this.m_hr;
//--- If failed to set the array size, inform of that and return 'false'
   if(::ArrayResize(this.m_array,size)!=size)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE,true);
      return false;
     }
//--- Set the index in the array for recording the image pixel
   int n=0;
//--- In a loop by the calculated height of the copied area, starting from the specified Y coordinate
   for(int y=y1;y<y1+this.m_hr;y++)
     {
      //--- in a loop by the calculated width of the copied area, starting from the specified X coordinate
      for(int x=x1;x<x1+this.m_wr;x++)
        {
         //--- Copy the next image pixel to the array and increase the array index
         this.m_array[n]=this.m_element.GetCanvasObj().PixelGet(x,y);
         n++;
        }
     }
//--- Successful - return 'true'
   return true;
  }
//+------------------------------------------------------------------+

各メソッド文字列は、コードで詳細に説明されています。つまり、コピーされた領域の初期座標がフォームの外側にある場合、コピーするものはないので、falseを返します。コピーされた領域の初期座標がフォームの座標と一致し、コピーされた領域の幅と高さがゼロに等しいか、フォームの幅と高さに一致する場合、フォーム画像全体がコピーされます。画像の一部のみを保存する必要がある場合は、最初にコピーされた幅と高さを計算して、フォームを超えないようにし、コピーされた領域にあるすべてのフォーム画像ピクセルをコピーします。

以下は、画像の一部または全体を配列からキャンバスにコピーするメソッドです。

//+------------------------------------------------------------------+
//| Copy the part or the entire image from the array to the canvas   |
//+------------------------------------------------------------------+
bool CPixelCopier::CopyImgDataToCanvas(const int x_coord,const int y_coord)
  {
//--- If the array of saved pixels is empty, inform of that and return 'false'
   int size=::ArraySize(this.m_array);
   if(size==0)
     {
      CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY,true);
      return false;
     }
//--- Set the index of the array for reading the image pixel
   int n=0;
//--- In a loop by the previously calculated height of the copied area, starting from the specified Y coordinate
   for(int y=y_coord;y<y_coord+this.m_hr;y++)
     {
      //--- in a loop by the previously calculated width of the copied area, starting from the specified X coordinate
      for(int x=x_coord;x<x_coord+this.m_wr;x++)
        {
         //--- Restore the next image pixel from the array and increase the array index
         this.m_element.GetCanvasObj().PixelSet(x,y,this.m_array[n]);
         n++;
        }
     }
   return true;
  }
//+------------------------------------------------------------------+

メソッドのロジックについては、コードコメントでも詳しく説明されています。 画像の一部を保存するメソッドとは異なり、コピーされた領域の座標とサイズを計算する必要はありません。これらはすべて、最初のメソッド操作後にクラス変数に保存されるためです。ここでは、復元された領域の各行を高さのループでピクセルごとにキャンバスにコピーするだけで、前のメソッドで保存された画像の一部を復元できます。

次に、フォームオブジェクトクラスから新しく作成されたクラスへのアクセスを調整します。

必要な数のコピーオブジェクトを動的に作成するので、フォームオブジェクトクラスでそのようなオブジェクトのリストを宣言する必要があります。新しく作成された各コピーオブジェクトがリストに追加され、そこから必要なオブジェクトへのポインタを取得して操作できるようになります。

クラスのprivateセクションで次のリストを宣言します

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // List of attached elements
   CArrayObj         m_list_pc_obj;                            // List of pixel copier objects
   CShadowObj       *m_shadow_obj;                             // Pointer to the shadow object
   color             m_color_frame;                            // Form frame color
   int               m_frame_width_left;                       // Form frame width to the left
   int               m_frame_width_right;                      // Form frame width to the right
   int               m_frame_width_top;                        // Form frame width at the top
   int               m_frame_width_bottom;                     // Form frame width at the bottom


同様のIDを持つ複数のコピーオブジェクトを持つことはできないため、指定されたIDを持つリスト内のオブジェクトプレゼンスフラグを返すメソッドが必要です。メソッドを宣言します

//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
   
//--- Return the flag indicating the presence of the copier object with the specified ID in the list
   bool              IsPresentPC(const int id);

public:

クラスのpublicセクションで、現在のフォームオブジェクトへのポインタを返すメソッドコピーオブジェクトのリストを返すメソッドを記述します。

public:
   //--- Constructors
                     CForm(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm(const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm(const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm() { this.Initialize(); }
//--- Destructor
                    ~CForm();
                           
//--- Supported form 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;                   }
   
//--- Return (1) itself, the list of (2) attached objects, (3) pixel copier objects and (4) the shadow object
   CForm            *GetObject(void)                                          { return &this;                  }
   CArrayObj        *GetList(void)                                            { return &this.m_list_elements;  }
   CArrayObj        *GetListPC(void)                                          { return &this.m_list_pc_obj;    }
   CGCnvElement     *GetShadowObj(void)                                       { return this.m_shadow_obj;      }

Next, declare the method creating a new image pixel copier object:

//--- Create a new pixel copier object
   CPixelCopier     *CreateNewPixelCopier(const int id,const int x_coord,const int y_coord,const int width,const int height);

//--- 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);

オブジェクトプロパティへの簡略化されたアクセスのメソッドを持つコードブロックの前に、画像ピクセルを操作するためのコードブロックを追加します。

//+------------------------------------------------------------------+
//| Methods of working with image pixels                             |
//+------------------------------------------------------------------+
//--- Return the pixel copier object by ID
   CPixelCopier     *GetPixelCopier(const int id);
//--- Copy the part or the entire image to the array
   bool              ImageCopy(const int id,const uint x_coord,const uint y_coord,uint &width,uint &height);
//--- Copy the part or the entire image from the array to the canvas
   bool              ImagePaste(const int id,const uint x_coord,const uint y_coord);
   
//+------------------------------------------------------------------+

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

以下は、リスト内の指定されたIDを持つコピーオブジェクトの存在を示すフラグを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the flag indicating the presence                          |
//| of the copier object with the specified ID in the list           |
//+------------------------------------------------------------------+
bool CForm::IsPresentPC(const int id)
  {
   for(int i=0;i<this.m_list_pc_obj.Total();i++)
     {
      CPixelCopier *pc=this.m_list_pc_obj.At(i);
      if(pc==NULL)
         continue;
      if(pc.ID()==id)
         return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

ここでは、コピーオブジェクトのリストに対する単純なループで次のオブジェクトを取得します。そのIDがメソッドに渡されたものと等しい場合は、trueを返します。ループが完了したら、falseを返します。

以下は、新しい画像ピクセルコピーオブジェクトを作成するメソッドです。

//+------------------------------------------------------------------+
//| Create a new pixel copier object                                 |
//+------------------------------------------------------------------+
CPixelCopier *CForm::CreateNewPixelCopier(const int id,const int x_coord,const int y_coord,const int width,const int height)
  {
//--- If the object with such an ID is already present, inform of that in the journal and return NULL
   if(this.IsPresentPC(id))
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST),(string)id);
      return NULL;
     }
//--- Create a new copier object with the specified parameters
   CPixelCopier *pc=new CPixelCopier(id,x_coord,y_coord,width,height,CGCnvElement::GetObject());
//--- If failed to create an object, inform of that and return NULL
   if(pc==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ));
      return NULL;
     }
//--- If failed to add the created object to the list, inform of that, remove the object and return NULL
   if(!this.m_list_pc_obj.Add(pc))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST)," ID: ",id);
      delete pc;
      return NULL;
     }
//--- Return the pointer to a newly created object
   return pc;
  }
//+------------------------------------------------------------------+

メソッドロジック全体は、コードへのコメントで説明されています。ご質問がある場合は、下のコメント欄でお気軽にお問い合わせください。

以下は、IDでピクセルコピーオブジェクトへのポインタを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the pixel copier object by ID                             |
//+------------------------------------------------------------------+
CPixelCopier *CForm::GetPixelCopier(const int id)
  {
   for(int i=0;i<this.m_list_pc_obj.Total();i++)
     {
      CPixelCopier *pc=m_list_pc_obj.At(i);
      if(pc==NULL)
         continue;
      if(pc.ID()==id)
         return pc;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

ここではすべてが簡単です。コピーのオブジェクトリストに対するループで、次のオブジェクトへのポインタを取得します。IDが必要なIDと一致する場合は、ポインタを返します。ループが完了した場合は指定されたIDのオブジェクトがリストに見つからないので、NULLを返します。

以下は、画像の一部または全部を配列にコピーするメソッドです。

//+------------------------------------------------------------------+
//| Copy part or all of the image to the array                       |
//+------------------------------------------------------------------+
bool CForm::ImageCopy(const int id,const uint x_coord,const uint y_coord,uint &width,uint &height)
  {
   CPixelCopier *pc=this.GetPixelCopier(id);
   if(pc==NULL)
     {
      pc=this.CreateNewPixelCopier(id,x_coord,y_coord,width,height);
      if(pc==NULL)
         return false;
     }
   return pc.CopyImgDataToArray(x_coord,y_coord,width,height);
  }
//+------------------------------------------------------------------+

ここでは、IDでコピーオブジェクトへのポインタを取得します。オブジェクトが見つからない場合は、そのことを通知してfalseを返します。オブジェクトへのポインタが正常に受信された場合は、上記で検討したコピーオブジェクトクラスのCopyImgDataToArray()メソッドの結果を返します。

以下は、画像の一部または全体を配列からキャンバスにコピーするメソッドです。

//+------------------------------------------------------------------+
//| Copy the part or the entire image from the array to the canvas   |
//+------------------------------------------------------------------+
bool CForm::ImagePaste(const int id,const uint x_coord,const uint y_coord)
  {
   CPixelCopier *pc=this.GetPixelCopier(id);
   if(pc==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST),(string)id);
      return false;
     }
   return pc.CopyImgDataToCanvas(x_coord,y_coord);
  }
//+------------------------------------------------------------------+

メソッドのロジックは、領域を配列に保存せず、代わりに配列から復元することを除いて、上記で検討したものと同じです。

これで、画像ピクセルコピーオブジェクトの動作をテストする準備が整いました。


検証

ピクセルコピーオブジェクトが正しく機能することを確認しましょう。記事の冒頭にあるGIF画像は、形状オブジェクトの背景に対して描画された後続の各画像が、以前に描画された画像にどのように重ね合わされるかを明確に示しています。次に、ピクセルコピーを使用して、テキストを重ね合わせる背景を最初に保存する必要があります。新しいテキストを描画する(描画されたテキストを視覚的に再配置する)前に、まずテキストが描画される背景を復元し(テキストを上書き)、新しい座標を使用して画像の一部を保存し、そこに次のテキストを表示します。これは、異なるアンカーポイントを持ち、テキストアンカーポイントに対応するフォーム側に表示される9つの表示テキストのそれぞれに対して行われます。これにより、テキストの下に保存された画像パーツの座標オフセットの計算の妥当性を確認できます。

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

EAはチャートに3つのフォームを表示します。縦のグラデーションで塗りつぶされた背景は、一番下のフォームで描画されます。ここでは、さらに4番目のフォームを描画し、水平方向のグラデーション塗りつぶしを実装します。テストされたテキストは、このフォームに表示されます。

EAグローバル変数の領域で、 4つのフォームオブジェクトを作成する必要があることを示します

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart78.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 (4)   // 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[];
//+------------------------------------------------------------------+

b0>OnInit() ハンドラで、前のフォームの座標に応じて新しいフォームの座標を計算します。後続の各フォームを作成した後、チャート全体を再描画する必要はありません。したがって、指定された文字列のフォーム更新メソッドにremoveを渡します(以前は、trueを明示的に渡しました)。この値は、最後のフォームを作成した後4番目のフォームを作成するための新しいコードブロックで最後に渡されます。

//+------------------------------------------------------------------+
//| 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++)
     {
      int y=40;
      if(i>0)
        {
         CForm *form_prev=list_forms.At(i-1);
         if(form_prev==NULL)
            continue;
         y=form_prev.BottomEdge()+10;
        }
      //--- When creating an object, pass all the required parameters to it
      CForm *form=new CForm("Form_0"+(string)(i+1),300,y,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();
        }
      //--- If this is the second 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();
        }
      //--- If this is the third 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,-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();
        }
      //--- If this is the fourth (bottom - tested) form
      if(i==3)
        {
         //--- 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,-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 horizontal gradient
         form.Erase(array_clr,form.Opacity(),false);
         //--- 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        
         //--- Specify the text parameters (text coordinates in the center of the form) and the anchor point (located at the center as well)
         string text=TextByLanguage("H-Градиент","H-Gradient");
         int text_x=form.Width()/2;
         int text_y=form.Height()/2;
         ENUM_TEXT_ANCHOR anchor=TEXT_ANCHOR_CENTER;
         
         //--- Find out the width and height of the outlining text rectangle (to be used as the size of the saved area)
         int text_w=0,text_h=0;
         form.TextSize(text,text_w,text_h);
         //--- Calculate coordinate offsets for the saved area depending on the text anchor point
         int shift_x=0,shift_y=0;
         form.TextGetShiftXY(text,anchor,shift_x,shift_y);
         
         //--- If a background area with calculated coordinates and size under the future text is successfully saved
         if(form.ImageCopy(0,text_x+shift_x,text_y+shift_y,text_w,text_h))
           {
            //--- Draw the text and update the form together with redrawing a chart
            form.Text(text_x,text_y,text,C'211,233,149',255,anchor);
            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)
     {
      //--- If the clicked object belongs to the EA
      if(StringFind(sparam,MQLInfoString(MQL_PROGRAM_NAME))==0)
        {
         //--- Get the object ID from it
         int form_id=(int)StringToInteger(StringSubstr(sparam,StringLen(sparam)-1))-1;
         //--- Find this form object in the loop by all forms created in the EA
         for(int i=0;i<list_forms.Total();i++)
           {
            CForm *form=list_forms.At(i);
            if(form==NULL)
               continue;
            //--- If the clicked object has the ID of 3 and the form has the same ID
            if(form_id==3 && form.ID()==3)
              {
               //--- Set the text parameters 
               string text=TextByLanguage("H-Градиент","H-Gradient");
               //--- Get the size of the future text
               int text_w=0,text_h=0;
               form.TextSize(text,text_w,text_h);
               //--- Get the anchor point of the last drawn text (this is the form which contained the last drawn text)
               ENUM_TEXT_ANCHOR anchor=form.TextAnchor();
               //--- Get the coordinates of the last drawn text
               int text_x=form.TextLastX();
               int text_y=form.TextLastY();
               //--- Calculate the coordinate offset of the saved rectangle area depending on the text anchor point
               int shift_x=0,shift_y=0;
               form.TextGetShiftXY(text,anchor,shift_x,shift_y);
               //--- Set the text anchor initial point (0 = LEFT_TOP) out of nine possible ones
               static int n=0;
               //--- If the previously copied form background image is successfully restored when creating the form object in OnInit()
               if(form.ImagePaste(0,text_x+shift_x,text_y+shift_y))
                 {
                  //--- Depending on the n variable, set the new text anchor point
                  switch(n)
                    {
                     case 0 : anchor=TEXT_ANCHOR_LEFT_TOP;     text_x=1;               text_y=1;               break;
                     case 1 : anchor=TEXT_ANCHOR_CENTER_TOP;   text_x=form.Width()/2;  text_y=1;               break;
                     case 2 : anchor=TEXT_ANCHOR_RIGHT_TOP;    text_x=form.Width()-2;  text_y=1;               break;
                     case 3 : anchor=TEXT_ANCHOR_LEFT_CENTER;  text_x=1;               text_y=form.Height()/2; break;
                     case 4 : anchor=TEXT_ANCHOR_CENTER;       text_x=form.Width()/2;  text_y=form.Height()/2; break;
                     case 5 : anchor=TEXT_ANCHOR_RIGHT_CENTER; text_x=form.Width()-2;  text_y=form.Height()/2; break;
                     case 6 : anchor=TEXT_ANCHOR_LEFT_BOTTOM;  text_x=1;               text_y=form.Height()-2; break;
                     case 7 : anchor=TEXT_ANCHOR_CENTER_BOTTOM;text_x=form.Width()/2;  text_y=form.Height()-2; break;
                     case 8 : anchor=TEXT_ANCHOR_RIGHT_BOTTOM; text_x=form.Width()-2;  text_y=form.Height()-2; break;
                     default: anchor=TEXT_ANCHOR_CENTER;       text_x=form.Width()/2;  text_y=form.Height()/2; break;
                    }
                  //--- According to the new anchor point, get the new offsets of the saved area coordinates
                  form.TextGetShiftXY(text,anchor,shift_x,shift_y);
                  //--- If the background area is successfully saved at new coordinates
                  if(form.ImageCopy(0,text_x+shift_x,text_y+shift_y,text_w,text_h))
                    {
                     //--- Draw the text in new coordinates and update the form
                     form.Text(text_x,text_y,text,C'211,233,149',255,anchor);
                     form.Update();
                    }
                  //--- Increase the object click counter (and also the pointer to the text anchor point),
                  //--- and if the value exceeds 8, reset the value to zero (from 0 to 8 = nine anchor points)
                  n++;
                  if(n>8) n=0;
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

コードコメントで詳細な説明を見つけてください。ご質問がある場合は、下のコメント欄でお気軽にお問い合わせください。

EAをコンパイルし、チャート上で起動します。

下部のフォームをクリックして、すべてが意図したとおりに機能することを確認しましょう。



次の段階

次の記事では、ライブラリでアニメーションコンセプトの開発を続け、スプライトアニメーションの作業を開始します。

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

目次に戻る

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

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

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

添付されたファイル |
MQL5.zip (4044.2 KB)
DoEasyライブラリのグラフィックス(第79部): 「アニメーションフレーム」オブジェクトクラスとその子孫オブジェクト DoEasyライブラリのグラフィックス(第79部): 「アニメーションフレーム」オブジェクトクラスとその子孫オブジェクト
本稿では、単一のアニメーションフレームとその子孫のクラスを開発します。このクラスでは、形状の下の背景を維持および復元しながら、形状を描画できるようにします。
取引のための組合せ論と確率論(第II部): ユニバーサルフラクタル 取引のための組合せ論と確率論(第II部): ユニバーサルフラクタル
本稿では、フラクタルの研究を続け、すべての資料の要約に特に注意を払います。これを行うために、これまでの開発をすべて、取引での実用化に便利で理解しやすいコンパクトな形にまとめてみます。
より優れたプログラマー(第01部): MQL5プログラマーとして成功するためにやめなければいけない5つのこと より優れたプログラマー(第01部): MQL5プログラマーとして成功するためにやめなければいけない5つのこと
初心者が最高のコーディングキャリアを築くのを妨げている悪い習慣はたくさんあります。これは上級プログラマーさえにも言えることです。この記事では、それらについて説明し、対処します。この記事は、MQL5で開発者として成功したいすべての人にとって必読です。
DoEasyライブラリのグラフィックス(第77部): 影オブジェクトクラス DoEasyライブラリのグラフィックス(第77部): 影オブジェクトクラス
本稿では、グラフィック要素オブジェクトの子孫である 影オブジェクトのクラスを作成し、オブジェクトの背景をグラデーションで塗りつぶす機能を追加します。