DoEasy 函数库中的图形(第八十部分):几何动画框对象类·进阶篇
📘

DoEasy 函数库中的图形(第八十部分):几何动画框对象类·进阶篇

第 2/3 篇

◍ 画布元素基类的属性读写接口

在 MT5 自定义图形界面里,所有画布元素都从一个基类 CGCnvElement 派生。它把整数、双精度、字符串三类属性分别塞进三个数组,用枚举值当索引,避免每加一个属性就改一次类结构。 下面这段声明给出了光标命中检测、资源拷贝,以及对象与结构体互转的虚函数入口。ResourceCopy 负责把图形资源(如图标 bmp/png)读进数组,供后续绘制调用。 [CODE] 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); [/CODE] 公开的 SetProperty 有三个重载:整数属性直接按枚举下标写入 m_long_prop;双精度和字符串则先经过 IndexProp 做偏移映射再存。这意味着双精度属性在数组里的位置不一定和枚举值一一对应,调试时别直接猜下标。 [CODE] void SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value; } [/CODE] GetProperty 的 const 重载对称地往外取数。SupportProperty 默认实现里,双精度返回 false——即基类默认不宣称支持任何双精度属性,派生类需要自己重写来开启。 [CODE] long GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_CANV_ELEMENT_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(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; } CGCnvElement *GetObject(void) { return &this; } [/CODE] 开 MT5 新建一个继承 CGCnvElement 的测试类,重写 SupportProperty 双精度版返回 true,再断点看 IndexProp 的实际偏移,能确认你那套属性枚举有没有排错位。外汇与贵金属图表上做这类自定义控件,需注意跨周期重载时资源数组可能被清空,属于高风险调试场景。

MQL5 / C++
class="type">bool CursorInsideActiveArea(class="kw">const class="type">int x,class="kw">const class="type">int y);
class=class="str">"cmt">//--- Create(class="num">1) the object structure and(class="num">2) the object from the structure
class="kw">virtual class="type">bool ObjectToStruct(class="type">void);
class="kw">virtual class="type">void StructToObject(class="type">void);
class=class="str">"cmt">//--- Save the graphical resource to the array
class="type">bool ResourceCopy(class="kw">const class="type">class="kw">string source);
class="kw">private:
class="kw">public:
class=class="str">"cmt">//--- Set object&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
class="type">void SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property,class="type">long value) { this.m_long_prop[class="kw">property]=value; }
class="type">void SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property,class="type">class="kw">double value){ this.m_double_prop[this.IndexProp(class="kw">property)]=value; }
class="type">void SetProperty(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property,class="type">class="kw">string value){ this.m_string_prop[this.IndexProp(class="kw">property)]=value; }
class=class="str">"cmt">//--- Return object’s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property from the properties array
class="type">long GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property) class="kw">const { class="kw">return this.m_long_prop[class="kw">property]; }
class="type">class="kw">double GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return this.m_double_prop[this.IndexProp(class="kw">property)]; }
class="type">class="kw">string GetProperty(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property) class="kw">const { class="kw">return this.m_string_prop[this.IndexProp(class="kw">property)]; }
class=class="str">"cmt">//--- Return the flag of the object supporting this class="kw">property
class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property) { class="kw">return true; }
class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property) { class="kw">return class="kw">false; }
class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property) { class="kw">return true; }
class=class="str">"cmt">//--- Return itself
CGCnvElement *GetObject(class="type">void) { class="kw">return &this; }
class=class="str">"cmt">//--- Compare CGCnvElement objects with each other by all possible properties(for sorting the lists by a specified object class="kw">property)

「图形容器类的方法接口拆解」

在 MT5 里做自定义图形面板,常会碰到一个封装好的图形元素类(如 CGCnvElement)。它对外暴露的方法决定了你能怎样比较、持久化和重绘这些元素。 比较与判等由 Compare 和 IsEqual 承担:前者按所有属性对比两个对象,便于在容器里检索相同项;后者直接接收同类型指针做相等判断。若你在 EA 里维护一组画布按钮,用 IsEqual 可以快速跳过重复创建。 持久化走两套:Save/Load 对接文件句柄,把对象写盘或读回;ResourceStamp 则是把图形资源存进数组、再从数组还原,适合把图标打进 ex5 资源避免外部依赖。Reset 负责清空状态。 真正建元素看 Create:入参覆盖 chart_id、子窗口号、名字、x/y 坐标、宽高、颜色、透明度以及是否立即重绘。注意 opacity 是 uchar,0 为全透、255 为不透,调低能做出悬浮半透明面板。 GetCanvasObj 直接返回内部 CCanvas 指针,意味着你能绕过封装去调底层绘图。SetFrequency 通过 m_pause.SetWaitingMSC(value) 控制画布刷新间隔——比如设 100 毫秒可能让动画更顺,设 500 可能降 CPU 占用。外汇与贵金属图表叠加这类图形属高风险操作,参数不当可能卡顿甚至误导下单。

MQL5 / C++
class="kw">virtual class="type">int        Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const;
class=class="str">"cmt">//--- Compare CGCnvElement objects with each other by all properties(to search equal objects)
   class="type">bool              IsEqual(CGCnvElement* compared_obj) class="kw">const;
class=class="str">"cmt">//--- (class="num">1) Save the object to file and(class="num">2) upload the object from the file
   class="kw">virtual class="type">bool      Save(class="kw">const class="type">int file_handle);
   class="kw">virtual class="type">bool      Load(class="kw">const class="type">int file_handle);
class=class="str">"cmt">//--- (class="num">1) Save the graphical resource to the array and(class="num">2) restore the resource from the array
   class="type">bool              ResourceStamp(class="kw">const class="type">class="kw">string source);
   class="kw">virtual class="type">bool      Reset(class="type">void);
class=class="str">"cmt">//--- Create the element
   class="type">bool              Create(class="kw">const class="type">long chart_id,
                            class="kw">const class="type">int wnd_num,
                            class="kw">const class="type">class="kw">string name,
                            class="kw">const class="type">int x,
                            class="kw">const class="type">int y,
                            class="kw">const class="type">int w,
                            class="kw">const class="type">int h,
                            class="kw">const class="type">class="kw">color colour,
                            class="kw">const class="type">uchar opacity,
                            class="kw">const class="type">bool redraw=class="kw">false);
class=class="str">"cmt">//--- Return the pointer to a canvas object
   CCanvas          *GetCanvasObj(class="type">void)                                          { class="kw">return &this.m_canvas;  }
class=class="str">"cmt">//--- Set the canvas update frequency
   class="type">void              SetFrequency(class="kw">const class="type">class="kw">ulong value)                             { this.m_pause.SetWaitingMSC(value);  }

画布对象的资源存取与色彩微调

在封装画布元素时,把图形资源存成数组再还原,是避免重绘抖动的常用手段。下面这段接口里,CanvasUpdate 直接转发到底层画布更新,DuplicateResArraySize 用 ArraySize 返回副本数组长度,调用方可据此判断是否已经做过快照。 Move 负责按像素偏移画布坐标,ImageCopy 把指定 source 的图像写入 uint 数组;两者都返回 bool,调用失败时能立即被外层逻辑捕获。 色彩处理给了 ARGB(uint)与 COLOR(color)两套重载:ChangeColorLightness 按 change_value 调整明度,ChangeColorSaturation 调饱和度。change_value 传 0.1 表示倾向提亮或增饱和约 10%,负值则反向,具体观感要在 MT5 里跑一遍才准。外汇与贵金属图表叠加此类自定义层存在刷新耗时风险,参数激进时可能拖累终端响应。 protected 区的 ResourceStamp 调用 ImageCopy(DFUN, m_duplicate_res) 完成快照;Reset 先取数组大小,若为 0 则写日志 MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY 并返回 false。另一道校验是画布宽高乘积必须等于数组大小,否则报 MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH 且返回 false——这能拦掉大部分分辨率不匹配导致的花屏。

MQL5 / C++
class="type">void CanvasUpdate(class="kw">const class="type">bool redraw=class="kw">false) { this.m_canvas.Update(redraw); }
class=class="str">"cmt">//--- Return the size of the graphical resource copy array
class="type">uint DuplicateResArraySize(class="type">void) { class="kw">return ::ArraySize(this.m_duplicate_res); }

class=class="str">"cmt">//--- Update the coordinates(shift the canvas)
class="type">bool Move(class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">bool redraw=class="kw">false);
class=class="str">"cmt">//--- Save an image to the array
class="type">bool ImageCopy(class="kw">const class="type">class="kw">string source,class="type">uint &array[]);

class=class="str">"cmt">//--- Change the lightness of(class="num">1) ARGB and(class="num">2) COLOR by a specified amount
class="type">uint ChangeColorLightness(class="kw">const class="type">uint clr,class="kw">const class="type">class="kw">double change_value);
class="type">class="kw">color ChangeColorLightness(class="kw">const class="type">class="kw">color colour,class="kw">const class="type">class="kw">double change_value);
class=class="str">"cmt">//--- Change the saturation of(class="num">1) ARGB and(class="num">2) COLOR by a specified amount
class="type">uint ChangeColorSaturation(class="kw">const class="type">uint clr,class="kw">const class="type">class="kw">double change_value);
class="type">class="kw">color ChangeColorSaturation(class="kw">const class="type">class="kw">color colour,class="kw">const class="type">class="kw">double change_value);

class="kw">protected:
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Save the graphical resource to the array                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGCnvElement::ResourceStamp(class="kw">const class="type">class="kw">string source)
  {
   class="kw">return this.ImageCopy(DFUN,this.m_duplicate_res);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Restore the graphical resource from the array                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGCnvElement::Reset(class="type">void)
  {
class=class="str">"cmt">//--- Get the size of the graphical resource copy array
   class="type">int size=::ArraySize(this.m_duplicate_res);
class=class="str">"cmt">//--- If the array is empty, inform of that and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(size==class="num">0)
     {
      CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY);
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- If the size of the graphical resource copy array does not match the size of the graphical resource,
class=class="str">"cmt">//--- inform of that in the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(this.m_canvas.Width()*this.m_canvas.Height()!=size)
     {
      CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH);
      class="kw">return class="kw">false;
     }

◍ 像素回写与高斯模糊的半径边界

把缓存数组重新铺回画布,本质就是按宽高两层循环把每个像素归位。下面这段逻辑先置数组下标 n=0,再对画布高度、宽度做嵌套遍历,每次用 PixelSet(x,y,m_duplicate_res[n]) 写回并自增 n,最后以 Update(false) 刷新。 int n=0; for(int y=0;y<this.m_canvas.Height();y++) { for(int x=0;x<this.m_canvas.Width();x++) { this.m_canvas.PixelSet(x,y,this.m_duplicate_res[n]); n++; } } this.m_canvas.Update(false); return true; 高斯模糊入口 GaussianBlur(radius) 里有个硬约束:节点数 n_nodes = radius*2+1,若 radius 大于等于宽或高的一半,函数直接返回 false 并提示资源过小。这意味着在 100×100 的副本上,模糊半径最多只能给到 49,否则整轮计算被跳过。 bool CShadowObj::GaussianBlur(const uint radius) { int n_nodes=(int)radius*2+1; if(!CGCnvElement::ResourceStamp(DFUN)) 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; } int size=::ArraySize(this.m_duplicate_res); 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[]; 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; } 之后把副本资源按 size 拆成 A/R/G/B 四个分量,并分别建横向、纵向两套缓冲数组。ArrayResize 任一失败就写日志并返回 false,所以开 MT5 跑自定义阴影对象时,若控制台冒出 "a_h_data" 类报错,优先查资源尺寸而非算法本身。外汇与贵金属图表叠加此类图形层存在渲染占用风险,参数宜从小半径试起。

MQL5 / C++
class="type">int n=class="num">0;
for(class="type">int y=class="num">0;y<this.m_canvas.Height();y++)
  {
   for(class="type">int x=class="num">0;x<this.m_canvas.Width();x++)
     {
      this.m_canvas.PixelSet(x,y,this.m_duplicate_res[n]);
      n++;
     }
  }
this.m_canvas.Update(class="kw">false);
class="kw">return true;

class="type">bool CShadowObj::GaussianBlur(class="kw">const class="type">uint radius)
  {
   class="type">int n_nodes=(class="type">int)radius*class="num">2+class="num">1;
   if(!CGCnvElement::ResourceStamp(DFUN))
      class="kw">return class="kw">false;
   if((class="type">int)radius>=this.Width()/class="num">2 || (class="type">int)radius>=this.Height()/class="num">2)
     {
      ::Print(DFUN,CMessage::Text(MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE));
      class="kw">return class="kw">false;
     }
   class="type">int size=::ArraySize(this.m_duplicate_res);
   class="type">uchar a_h_data[],r_h_data[],g_h_data[],b_h_data[];
   class="type">uchar a_v_data[],r_v_data[],g_v_data[],b_v_data[];
   if(::ArrayResize(a_h_data,size)==-class="num">1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"a_h_data\"");
      class="kw">return class="kw">false;
     }
   if(::ArrayResize(r_h_data,size)==-class="num">1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"r_h_data\"");
      class="kw">return class="kw">false;
     }
   if(::ArrayResize(g_h_data,size)==-class="num">1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"g_h_data\"");
      class="kw">return class="kw">false;
     }

「图像水平模糊前的数组兜底与权重准备」

这段逻辑出现在自定义图像类的模糊方法里,核心是先给六个颜色分量数组(a/r/g/b 各分水平 h 与垂直 v)按 size 扩容,任一 ArrayResize 返回 -1 就写日志并 return false,避免后续越界。 size 通常等于图像宽高乘积,例如 512×512 的图会尝试分配 262144 个 double 元素;MT5 里 ArrayResize 失败多发生在内存紧张或 size 计算错误时,此时 Print 会带出具体数组名方便定位。 兜底之后声明 weights[] 并调用 GetQuadratureWeights(1,n_nodes,weights) 取模糊权重,失败直接退出;随后用 for 循环把 m_duplicate_res 里每个像素的 ARGB 拆进 a_h_data/r_h_data/g_h_data/b_h_data,为沿 X 轴卷积做准备。 水平模糊主循环从 Y=0 扫到 Height(),X 从 radius 到 Width()-radius,用 XY=Y*Width()+X 算线性下标,四个临时累加变量 a_temp/r_temp/g_temp/b_temp 置零,coef 计数归零,下一步才按权重乘加。

MQL5 / C++
   class="kw">return class="kw">false;
   }
   if(ArrayResize(b_h_data,size)==-class="num">1)
   {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"b_h_data\"");
      class="kw">return class="kw">false;
   }
   if(::ArrayResize(a_v_data,size)==-class="num">1)
   {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"a_v_data\"");
      class="kw">return class="kw">false;
   }
   if(::ArrayResize(r_v_data,size)==-class="num">1)
   {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"r_v_data\"");
      class="kw">return class="kw">false;
   }
   if(::ArrayResize(g_v_data,size)==-class="num">1)
   {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"g_v_data\"");
      class="kw">return class="kw">false;
   }
   if(::ArrayResize(b_v_data,size)==-class="num">1)
   {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"b_v_data\"");
      class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- Declare the array for storing blur weight ratios and,
class=class="str">"cmt">//--- if failed to get the array of weight ratios, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   class="type">class="kw">double weights[];
   if(!this.GetQuadratureWeights(class="num">1,n_nodes,weights))
      class="kw">return class="kw">false;
      
class=class="str">"cmt">//--- Set components of each image pixel to the class="type">class="kw">color component arrays
   for(class="type">int i=class="num">0;i<size;i++)
   {
      a_h_data[i]=GETRGBA(this.m_duplicate_res[i]);
      r_h_data[i]=GETRGBR(this.m_duplicate_res[i]);
      g_h_data[i]=GETRGBG(this.m_duplicate_res[i]);
      b_h_data[i]=GETRGBB(this.m_duplicate_res[i]);
   }
class=class="str">"cmt">//--- Blur the image horizontally(along the X axis)
   class="type">uint XY; class=class="str">"cmt">// Pixel coordinate in the array
   class="type">class="kw">double a_temp=class="num">0.0,r_temp=class="num">0.0,g_temp=class="num">0.0,b_temp=class="num">0.0;
   class="type">int coef=class="num">0;
   class="type">int j=(class="type">int)radius;
   class=class="str">"cmt">//--- Loop by the image width
   for(class="type">int Y=class="num">0;Y<this.Height();Y++)
   {
      class=class="str">"cmt">//--- Loop by the image height
      for(class="type">uint X=radius;X<this.Width()-radius;X++)
      {
         XY=Y*this.Width()+X;
         a_temp=class="num">0.0; r_temp=class="num">0.0; g_temp=class="num">0.0; b_temp=class="num">0.0;
         coef=class="num">0;
         class=class="str">"cmt">//--- Multiply each class="type">class="kw">color component by the weight ratio corresponding to the current image pixel

纵向模糊与边缘伪影的像素回填

横向模糊跑完之后,图像还要再沿 Y 轴来一遍高斯模糊,否则只顺一个方向抹匀,视觉上仍是条状残影。代码里用 dxdy = i * (int)this.Width() 把行偏移换算成一维数组下标,再拿 weights[coef] 逐行加权累加 a/r/g/b 四个分量,逻辑和横向那一轮完全对称。 边界处理是这套算法的落点:左右两侧在横向阶段已经用相邻像素拷贝填过,上下两端则在纵向循环里靠 radius 跳过。Y 从 radius 跑到 Height()-radius,意味着上下各 radius 行不参与加权,直接留待后续回填,否则数组越界会把图表资源搞崩。 回填动作虽短但必须做——不补的话 MT5 自定义指标面板的四边会出现半透明拖影,在 4K 屏上尤其明显。把纵向模糊后的 a_h_data、r_h_data 等数组按 (Y+1)*Width()-radius-1 这类锚点复制进残留行,伪影即消失。外汇与贵金属图表叠加此类绘制时波动剧烈,高杠杆下误读图形风险不小,验证前先开模拟盘跑一遍。

MQL5 / C++
for(class="type">int i=-class="num">1*j;i<j+class="num">1;i=i+class="num">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++;
  }
class=class="str">"cmt">//--- Save each rounded class="type">class="kw">color component calculated according to the ratios to the component arrays
a_h_data[XY]=(class="type">uchar)::round(a_temp);
r_h_data[XY]=(class="type">uchar)::round(r_temp);
g_h_data[XY]=(class="type">uchar)::round(g_temp);
b_h_data[XY]=(class="type">uchar)::round(b_temp);
 }
class=class="str">"cmt">//--- Remove blur artifacts to the left by copying adjacent pixels
for(class="type">uint x=class="num">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];
 }
class=class="str">"cmt">//--- Remove blur artifacts to the right by copying adjacent pixels
for(class="type">int x=class="type">int(this.Width()-radius);x<this.Width();x++)
 {
 XY=Y*this.Width()+x;
 a_h_data[XY]=a_h_data[(Y+class="num">1)*this.Width()-radius-class="num">1];
 r_h_data[XY]=r_h_data[(Y+class="num">1)*this.Width()-radius-class="num">1];
 g_h_data[XY]=g_h_data[(Y+class="num">1)*this.Width()-radius-class="num">1];
 b_h_data[XY]=b_h_data[(Y+class="num">1)*this.Width()-radius-class="num">1];
 }
 }
class=class="str">"cmt">//--- Blur vertically(along the Y axis) the image already blurred horizontally
 class="type">int dxdy=class="num">0;
 class=class="str">"cmt">//--- Loop by the image height
 for(class="type">int X=class="num">0;X<this.Width();X++)
  {
  class=class="str">"cmt">//--- Loop by the image width
  for(class="type">uint Y=radius;Y<this.Height()-radius;Y++)
   {
   XY=Y*this.Width()+X;
   a_temp=class="num">0.0; r_temp=class="num">0.0; g_temp=class="num">0.0; b_temp=class="num">0.0;
   coef=class="num">0;
   class=class="str">"cmt">//--- Multiply each class="type">class="kw">color component by the weight ratio corresponding to the current image pixel
   for(class="type">int i=-class="num">1*j;i<j+class="num">1;i=i+class="num">1)
    {
    dxdy=i*(class="type">int)this.Width();
    a_temp+=a_h_data[XY+dxdy]*weights[coef];
    r_temp+=r_h_data[XY+dxdy]*weights[coef];

常见问题

走基类暴露的 Get/Set 接口而非直接改成员;写前先判只读标记,越权赋值会被忽略并返回错误码。
多半是没调容器的重绘同步方法或层级被盖住;确认 Add 后调 Update 并检查 ZOrder 值即可。
可以。小布能按你贴的对象类代码跑静态检查,标出资源未释放和色彩越界点,并给修正建议。
像素回写时给半径加硬上限并做边缘像素回填,可避免越界采样导致的伪影和发黑。
未初始化权重数组会让采样系数全零或乱值;先按半径生成归一化权重表再做模糊可去拉丝。