DoEasy 函数库中的图形(第七十八部分):函数库中的动画原理。 图片切分(基础篇)
「在 DoEasy 里给图形做动画切图」
DoEasy 函数库做到第七十八部分时,已经把图形对象的动画原理铺开了,核心思路是把一张大图按帧切分成小块,再逐帧贴到图表上形成动效。这套机制不依赖外部 GIF,全部用库内对象和资源管理实现,MT5 图表上跑起来延迟可控。 原文给出的示例发布于 2021 年 8 月 12 日 11:08,作者 Artyom Trishkin,截至文档统计该帖浏览量 3767、评论 0,说明这类底层切图玩法在普通用户里还偏冷门。 想验证的话,直接在 MT5 打开 DoEasy 对应版本,找到图形资源类里负责复制粘贴图像部分的实现,把一张横排序列帧图塞进去,看图表对象是否按索引切片刷新。外汇与贵金属图表加载自定义图形属高风险操作环境,参数没配对可能导致对象重绘卡顿。
灵动动画的最小单元:背景存取与重绘
图形界面里显示的数据大多会随时间变化,GUI 元素需要用视觉效果响应用户操作。若直接在画布上逐帧绘制静态图像而不做清理,各帧会彼此重叠成一团乱码——我曾把同一段文本画在会话窗不同坐标,结果就是一堆错位像素。 避免混乱有两种路数。其一是在每帧重绘前彻底擦掉旧图、重铺背景再画新内容,但这只适合尺寸小、结构简单的会话窗;其二是把叠加图像那块的背景暂存进数组,移动时先用存好的背景覆盖旧位置“复原”,再于新坐标绘制,相当于背景在内存里被持续周转。 我引入函数库的最小动画元素只有三步:在指定坐标保存背景、在该坐标显示图像、重绘时恢复背景。为此写了一个轻量类,负责存坐标与尺寸,并提供“按坐标存背景”和“将背景写入数组”两个方法,尺寸和坐标随背景一并留在类变量里。 之所以用类而非给会话窗挂两个方法,是因为单文本或单图时尚可应付;若会话窗多处同时跑动画,每个实例自理坐标与背景会更干净。一个实例就是一条可加入列表动态处理的图像序列,后续也能拿已绘内容当背景继续叠加。
◍ 给图形元素类加像素快照能力
做 MT5 自定义 GUI 时,最烦的是在画布上叠了文本或图像之后,想恢复原背景却只能重绘一遍。重绘慢且容易闪,更直接的办法是在对象刚创建、外观定型后,把 CCanvas 的图形资源像素整块读进数组存着,以后要恢复只需数组间复制。 在 GCnvElement.mqh 的受保护区,我加了一个数组用于缓存初始像素,以及一个把 CCanvas 实例图形资源存进数组的方法。代价是占一点内存,但换来了随时一键还原界面元素原始外观的可能。
| 文本绘制有 9 个锚点(如 Right | Bottom 是右下角起始),而函数库初始坐标统一是矩形左上角 Left | Top。若直接按初始坐标存图,文本会跑到保存图像的右下角,背景区域对不上。所以必须先用 CCanvas 的 TextSize() 算轮廓宽高,再按对齐方式算 X/Y 偏移量,把背景正确存进数组。 |
|---|
私密区另存了最后绘制文本的 X、Y 坐标变量,在公开区提供返回指针和保存图像的方法。下面这段消息索引是配套加进 Data.mqh 的,其中高亮部分是给图形元素和像素复制器类新补的报错文本:
class=class="str">"cmt">//--- CChartObjCollection MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION, class=class="str">"cmt">// Chart collection MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ, class=class="str">"cmt">// Failed to create a new chart object MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART, class=class="str">"cmt">// Failed to add a chart object to the collection MSG_CHART_COLLECTION_ERR_CHARTS_MAX, class=class="str">"cmt">// Cannot open new chart. Number of open charts at maximum MSG_CHART_COLLECTION_CHART_OPENED, class=class="str">"cmt">// Chart opened MSG_CHART_COLLECTION_CHART_CLOSED, class=class="str">"cmt">// Chart closed MSG_CHART_COLLECTION_CHART_SYMB_CHANGED, class=class="str">"cmt">// Chart symbol changed MSG_CHART_COLLECTION_CHART_TF_CHANGED, class=class="str">"cmt">// Chart timeframe changed MSG_CHART_COLLECTION_CHART_SYMB_TF_CHANGED, class=class="str">"cmt">// Chart symbol and timeframe changed class=class="str">"cmt">//--- CGCnvElement MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY, class=class="str">"cmt">// Error! Empty array class=class="str">"cmt">//--- CForm MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,class=class="str">"cmt">// No shadow object. Create it using the CreateShadowObj() method MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ, class=class="str">"cmt">// Failed to create new shadow object MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ, class=class="str">"cmt">// Failed to create new pixel copier object MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST, class=class="str">"cmt">// Pixel copier object with ID already present in the list MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST, class=class="str">"cmt">// No pixel copier object with ID in the list class=class="str">"cmt">//--- CShadowObj MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE, class=class="str">"cmt">// Error! Image size too small or blur too extensive }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- 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"},
「图形元素类的内部报错与成员布局」
在 MQL5 自定义图形库里,CGCnvElement 这类图形元素对象把运行时提示做成了俄英双语文案表,方便调试时直接定位状态。比如图表事件会返回「Open chart / Closed chart」以及「Changed chart symbol」「Changed chart timeframe」,而像素拷贝器出错则提示「Failed to create new pixel copier object」或「There is already a pixel copier object in the list with ID 」。 从类声明看,CGCnvElement 继承自 CGBaseObj,保护成员里挂了 CCanvas 做绘制、CPause 做停顿控制,还有 m_shadow 标记是否带阴影、m_chart_color_bg 存图表背景色。值得注意的是 m_data_array[] 这个 uint 数组,专门用来缓存资源数据的副本,若传入空数组会触发「Error! Empty array」的硬报错。 图像尺寸异常也会直接抛错:当模糊半径过大或图像太小,类会返回「Error! Image size is very small or very large blur」。开 MT5 把这段报错表接进你的 EA 日志,能省掉一半图形对象联调时间;外汇与贵金属图表上做这类自定义控件,需注意滑点与时延带来的高风险。
class CGCnvElement : class="kw">public CGBaseObj { class="kw">protected: CCanvas m_canvas; class=class="str">"cmt">// CCanvas class object CPause m_pause; class=class="str">"cmt">// Pause class object class="type">bool m_shadow; class=class="str">"cmt">// Shadow presence class="type">color m_chart_color_bg; class=class="str">"cmt">// Chart background class="type">color class="type">uint m_data_array[]; class=class="str">"cmt">// Array for storing resource data copy class=class="str">"cmt">//--- Return the cursor position relative to the(class="num">1) entire element and(class="num">2) the element&class="macro">#x27;s active area class="type">bool CursorInsideElement(class="kw">const class="type">int x,class="kw">const class="type">int y); 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
画布元素基类的属性与虚函数骨架
在 MT5 自定义图形库里,CGCnvElement 作为画布元素的基类,用三块定长数组分别承接订单的整数、实数与字符串属性:m_long_prop[ORDER_PROP_INTEGER_TOTAL]、m_double_prop[ORDER_PROP_DOUBLE_TOTAL]、m_string_prop[ORDER_PROP_STRING_TOTAL]。这种分桶存储让后续派生类按属性类型直接索引,不必做运行时类型判断。 文本锚点相关字段暴露了渲染细节:m_text_anchor 控制对齐方式,m_text_x 与 m_text_y 记录上一次绘制的像素坐标,m_color_bg 与 m_opacity 管背景色和透明度(0~255 的 uchar)。你在调面板透明度时改的就是 m_opacity。 虚函数 SupportProperty 的三个重载直接决定了对象能力边界——整数与字符串属性返回 true,双精度属性返回 false。这意味着基类默认不支持 double 类属性,若你的标签要显示浮点价格,得在派生类里重写这个重载。 比较与持久化接口给了列表排序和存取可能:Compare 按 mode 做多属性比对,IsEqual 做全等判断,Save 配合 file_handle 写盘。下面这段声明是基类成员的原貌,逐行拆完就能照抄进自己的 EA 面板。
class="type">bool ResourceCopy(class="kw">const class="type">class="kw">string source); class="kw">private: class="type">long m_long_prop[ORDER_PROP_INTEGER_TOTAL]; class=class="str">"cmt">// Integer properties class="type">class="kw">double m_double_prop[ORDER_PROP_DOUBLE_TOTAL]; class=class="str">"cmt">// Real properties class="type">class="kw">string m_string_prop[ORDER_PROP_STRING_TOTAL]; class=class="str">"cmt">// String properties ENUM_TEXT_ANCHOR m_text_anchor; class=class="str">"cmt">// Current text alignment class="type">int m_text_x; class=class="str">"cmt">// Text last X coordinate class="type">int m_text_y; class=class="str">"cmt">// Text last Y coordinate class="type">color m_color_bg; class=class="str">"cmt">// Element background class="type">color class="type">uchar m_opacity; class=class="str">"cmt">// Element opacity class=class="str">"cmt">//--- Return the index of the array the order&class="macro">#x27;s(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are located at 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 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) 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);
◍ 图形元素类的接口暴露了什么
在自定义图形库里,一个元素对象对外只靠几个虚函数和 inline 方法撑起生命周期。Load 是虚函数,接收文件句柄把资源读进实例,派生类可以重写它来决定怎么解析二进制。 Create 负责在指定图表子窗口里落位:chart_id 定位图表,wnd_num 是子窗口序号,name 做唯一标识,x/y/w/h 是像素级矩形,colour 和 opacity 控制填充与透明度,redraw 默认 false 避免每次创建都重绘。 GetCanvasObj 直接返回内部 CCanvas 的指针,意味着你拿到的是 &this.m_canvas,可绕过封装往画布写像素。SetFrequency 把等待毫秒塞给 m_pause.SetWaitingMSC,这决定了回调刷新的节流间隔,比如传 100 就倾向每 100ms 重算一次。 Move 用新 x/y 平移整个画布,ImageCopy 把 named source 的位图拷进 uint 数组,这两步是做动态贴图和水印的基础。外汇与贵金属图表叠加自绘层波动剧烈,高杠杆下误绘可能放大误判,验证前请在模拟盘跑通。
class="kw">virtual class="type">bool Load(class="kw">const class="type">int file_handle); 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">color colour, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool redraw=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); } 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=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[]);