DoEasy 函数库中的图形(第七十五部分):处理基本图形元素图元和文本的方法·进阶篇
🎨

DoEasy 函数库中的图形(第七十五部分):处理基本图形元素图元和文本的方法·进阶篇

(2/3)· 上篇搭好图形元素骨架,本篇补齐基元绘制、文本锚点与文件存读的方法缺口

含代码示例 第 2/3 篇
用 CCanvas 画图时直接 Erase(0) 清画布,图表切周期后常留下半透明残影。本篇把透明通道清法和文本对齐锚点一次讲清,省得你后面调 GUI 时反复踩坑。

◍ 颜色处理的底层静态接口

在 MT5 自定义指标里做可视化增强时,直接操作 color 类型往往不够,需要拆通道、混色、做渐变。下面这组静态方法把常用色彩运算封装好了,开 MT5 新建一个类把声明贴进去就能用。 ColorToRGB 把 color 拆成 R/G/B 三个 double 引用返回,GetR/GetG/GetB/GetA 则单独取某一通道;A 通道在 MT5 里默认 255,做透明度混合时得自己留意。 MixColors 按权重 aK 在俩颜色间线性插值,BlendColors 用 uint 接收上下层颜色做叠加,Gradient 能从一个颜色数组生成指定数量的渐变输出,aCycle 开 true 会首尾相接循环。 RGBtoXYZsimple 与 XYZtoRGBsimple 是简易色彩空间转换,Negative 取反色,RGBtoGray 与 RGBtoGraySimple 把彩色转灰度——后者权重均等,前者按亮度感知加权,肉眼更顺。

MQL5 / C++
class="kw">static class="type">void       ColorToRGB(class="kw">const class="type">class="kw">color aColor,class="type">class="kw">double &aR,class="type">class="kw">double &aG,class="type">class="kw">double &aB);
class="kw">static class="type">class="kw">double     GetR(class="kw">const class="type">class="kw">color aColor);
class="kw">static class="type">class="kw">double     GetG(class="kw">const class="type">class="kw">color aColor);
class="kw">static class="type">class="kw">double     GetB(class="kw">const class="type">class="kw">color aColor);
class="kw">static class="type">class="kw">double     GetA(class="kw">const class="type">class="kw">color aColor);
class="kw">static class="type">class="kw">color      RGBToColor(class="kw">const class="type">class="kw">double aR,class="kw">const class="type">class="kw">double aG,class="kw">const class="type">class="kw">double aB);
class="kw">static class="type">class="kw">color      MixColors(class="kw">const class="type">class="kw">color aCol1,class="kw">const class="type">class="kw">color aCol2,class="kw">const class="type">class="kw">double aK);
class="kw">static class="type">class="kw">color      BlendColors(class="kw">const class="type">uint lower_color,class="kw">const class="type">uint upper_color);
class="kw">static class="type">void       Gradient(class="type">class="kw">color &aColors[],class="type">class="kw">color &aOut[],class="type">int aOutCount,class="type">bool aCycle=class="kw">false);
class="kw">static class="type">void       RGBtoXYZsimple(class="type">class="kw">double aR,class="type">class="kw">double aG,class="type">class="kw">double aB,class="type">class="kw">double &oX,class="type">class="kw">double &oY,class="type">class="kw">double &oZ);
class="kw">static class="type">void       XYZtoRGBsimple(class="kw">const class="type">class="kw">double aX,class="kw">const class="type">class="kw">double aY,class="kw">const class="type">class="kw">double aZ,class="type">class="kw">double &oR,class="type">class="kw">double &oG,class="type">class="kw">double &oB);
class="kw">static class="type">class="kw">color      Negative(class="kw">const class="type">class="kw">color aColor);
class="kw">static class="type">class="kw">color      StandardColor(class="kw">const class="type">class="kw">color aColor,class="type">int &aIndex);
class="kw">static class="type">class="kw">double     RGBtoGray(class="type">class="kw">double aR,class="type">class="kw">double aG,class="type">class="kw">double aB);
class="kw">static class="type">class="kw">double     RGBtoGraySimple(class="type">class="kw">double aR,class="type">class="kw">double aG,class="type">class="kw">double aB);

给颜色算坐标:从极坐标到 XYZ 的底層函数

做 MT5 自定义指标时,若想用 Lab 色差给 K 线背景做视觉聚类,得先搞定几个颜色空间转换的底层函数。下面这段 CColors 类的方法,把角度和 RGB 都转成了可计算的数值,直接抄进 EA 或脚本就能用。 Arctan2 是标准四象限反正切,y=0 时按 x 正负返回 0 或 π;x>0 走 atan(y/x),x<0 则根据 y 符号加减 π 把相位拉回正确象限。它比系统 atan 更稳,不会在坐标轴边界丢符号。 Hue_To_RGB 处理 HSL 里 hue 到单通道的映射:vH 先归一化到 [0,1),再按 6vH、2vH、3vH 三个分段做线性插值,阈值写死 1/6、1/2、2/3。这种写法比查表省内存,MT5 回测时每根 bar 调一次也几乎不拖速。 RGBtoXYZ 先把 0–255 的通道除以 255,再以 0.04045 为分界做 gamma 解码——大于阈值的走 pow((c+0.055)/1.055, 2.4),小于等于则除以 12.92,最后乘 100 拉伸到 CIE 参考白尺度。外汇和贵金属图表的高频重绘场景里,这类转换若放在 OnCalculate 反复跑,建议缓存结果,毕竟贵金属跳空时重绘压力大。

MQL5 / C++
class="type">class="kw">double CColors::Arctan2(class="kw">const class="type">class="kw">double x,class="kw">const class="type">class="kw">double y)
  {
   if(y==class="num">0)
      class="kw">return(x<class="num">0 ? M_PI : class="num">0);
   else
     {
      if(x>class="num">0)
         class="kw">return(::atan(y/x));
      if(x<class="num">0)
         class="kw">return(y>class="num">0 ? atan(y/x)+M_PI : atan(y/x)-M_PI);
      else
         class="kw">return(y<class="num">0 ? -M_PI_2 : M_PI_2);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Hue_To_RGB                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CColors::Hue_To_RGB(class="type">class="kw">double v1,class="type">class="kw">double v2,class="type">class="kw">double vH)
  {
   if(vH<class="num">0)
      vH+=class="num">1.0;
   if(vH>class="num">1.0)
      vH-=class="num">1;
   if((class="num">6.0*vH)<class="num">1.0)
      class="kw">return(v1+(v2-v1)*class="num">6.0*vH);
   if((class="num">2.0*vH)<class="num">1.0)
      class="kw">return(v2);
   if((class="num">3.0*vH)<class="num">2.0)
      class="kw">return(v1+(v2-v1)*((class="num">2.0/class="num">3.0)-vH)*class="num">6.0);
class=class="str">"cmt">//---
   class="kw">return(v1);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Conversion of RGB into XYZ                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CColors::RGBtoXYZ(class="kw">const class="type">class="kw">double aR,class="kw">const class="type">class="kw">double aG,class="kw">const class="type">class="kw">double aB,class="type">class="kw">double &oX,class="type">class="kw">double &oY,class="type">class="kw">double &oZ)
  {
   class="type">class="kw">double var_R=aR/class="num">255;
   class="type">class="kw">double var_G=aG/class="num">255;
   class="type">class="kw">double var_B=aB/class="num">255;
class=class="str">"cmt">//---
   if(var_R>class="num">0.04045)
      var_R=::pow((var_R+class="num">0.055)/class="num">1.055,class="num">2.4);
   else
      var_R=var_R/class="num">12.92;
class=class="str">"cmt">//---
   if(var_G>class="num">0.04045)
      var_G=::pow((var_G+class="num">0.055)/class="num">1.055,class="num">2.4);
   else
      var_G=var_G/class="num">12.92;
class=class="str">"cmt">//---
   if(var_B>class="num">0.04045)
      var_B=::pow((var_B+class="num">0.055)/class="num">1.055,class="num">2.4);
   else
      var_B=var_B/class="num">12.92;
class=class="str">"cmt">//---
   var_R =var_R*class="num">100.0;
   var_G =var_G*class="num">100.0;

「XYZ与RGB、Yxy的双向换算细节」

把色彩空间接到行情面板之前,得先搞清楚 CColors 类里 XYZ 和 RGB、Yxy 互转的矩阵与阈值。RGB 转 XYZ 时,B 通道先乘 100.0 做归一化,再用三组系数合成:oX 取 R*0.4124+G*0.3576+B*0.1805,oY 取 R*0.2126+G*0.7152+B*0.0722,oZ 取 R*0.0193+G*0.1192+B*0.9505。 反向 XYZtoRGB 先将 XYZ 各除以 100,再套用逆矩阵(如 var_R = X*3.2406 + Y*(-1.5372) + Z*(-0.4986))。关键在伽马校正:当某通道值 >0.0031308 时走 1.055*pow(c, 1/2.4)-0.055,否则直接 12.92*c,最后乘 255 得到 0–255 的屏幕色值。 Yxy 只是 XYZ 的归一化变形:XYZtoYxy 里 ox=X/(X+Y+Z)、oy=Y/(X+Y+Z),亮度 Y 原样保留;YxyToXYZ 则靠 oX=x*(Y/y)、oZ=(1-x-y)*(Y/y) 还原。这三个函数连起来,就能把任意指标数值映射成可视色而不丢失明度信息。 在 MT5 里新建 EA 把这段直接塞进 CColors,传一组 (X=95.05, Y=100, Z=108.9) 进去,RGB 应落在约 (255,255,255) 附近;若偏差大,优先查除以 100 和伽马分支是否漏写。外汇与贵金属图表配色涉及主观判断,市场高风险,色域映射仅辅助观察、不构成方向建议。

MQL5 / C++
  var_B =var_B*class="num">100.0;
  oX     =var_R*class="num">0.4124+var_G*class="num">0.3576+var_B*class="num">0.1805;
  oY     =var_R*class="num">0.2126+var_G*class="num">0.7152+var_B*class="num">0.0722;
  oZ     =var_R*class="num">0.0193+var_G*class="num">0.1192+var_B*class="num">0.9505;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Conversion of XYZ into RGB                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CColors::XYZtoRGB(class="kw">const class="type">class="kw">double aX,class="kw">const class="type">class="kw">double aY,class="kw">const class="type">class="kw">double aZ,class="type">class="kw">double &oR,class="type">class="kw">double &oG,class="type">class="kw">double &oB)
  {
  class="type">class="kw">double var_X =aX/class="num">100;
  class="type">class="kw">double var_Y =aY/class="num">100;
  class="type">class="kw">double var_Z =aZ/class="num">100;
  class="type">class="kw">double var_R =var_X*class="num">3.2406+var_Y*-class="num">1.5372+var_Z*-class="num">0.4986;
  class="type">class="kw">double var_G =var_X*(-class="num">0.9689)+var_Y*class="num">1.8758+var_Z*class="num">0.0415;
  class="type">class="kw">double var_B =var_X*class="num">0.0557+var_Y*(-class="num">0.2040)+var_Z*class="num">1.0570;
class=class="str">"cmt">//---
  if(var_R>class="num">0.0031308)
      var_R=class="num">1.055*(::pow(var_R,class="num">1.0/class="num">2.4))-class="num">0.055;
  else
      var_R=class="num">12.92*var_R;
class=class="str">"cmt">//---
  if(var_G>class="num">0.0031308)
      var_G=class="num">1.055*(::pow(var_G,class="num">1.0/class="num">2.4))-class="num">0.055;
  else
      var_G=class="num">12.92*var_G;
class=class="str">"cmt">//---
  if(var_B>class="num">0.0031308)
      var_B=class="num">1.055*(::pow(var_B,class="num">1.0/class="num">2.4))-class="num">0.055;
  else
      var_B=class="num">12.92*var_B;
class=class="str">"cmt">//---
  oR =var_R*class="num">255.0;
  oG =var_G*class="num">255.0;
  oB =var_B*class="num">255.0;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Conversion of XYZ into Yxy                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CColors::XYZtoYxy(class="kw">const class="type">class="kw">double aX,class="kw">const class="type">class="kw">double aY,class="kw">const class="type">class="kw">double aZ,class="type">class="kw">double &oY,class="type">class="kw">double &ox,class="type">class="kw">double &oy)
  {
  oY =aY;
  ox =aX/(aX+aY+aZ);
  oy =aY/(aX+aY+aZ);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Conversion of Yxy into XYZ                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CColors::YxyToXYZ(class="kw">const class="type">class="kw">double aY,class="kw">const class="type">class="kw">double ax,class="kw">const class="type">class="kw">double ay,class="type">class="kw">double &oX,class="type">class="kw">double &oY,class="type">class="kw">double &oZ)
  {
  oX =ax*(aY/ay);
  oY =aY;
  oZ =(class="num">1.0-ax-ay)*(aY/ay);
  }

◍ 色彩空间互转的底层函数怎么写

在 MT5 里做自定义指标面板时,经常要把设备无关的 XYZ 三色值映射到 HunterLab 或 CIELab,方便按人眼感知差异做配色告警。下面这组 CColors 类方法直接给出了双向换算,复制进 EA 或指标源码就能编译跑。 XYZtoHunterLab 用的是经验系数:L=10·√Y,a=17.5·((1.02X−Y)/√Y),b=7·((Y−0.847Z)/√Y)。注意它没做白点校正,适合快速可视化,不适合印刷级校色。 反向 HunterLabToXYZ 先算出 var_Y=L/10、var_X=a/17.5·L/10、var_Z=b/7·L/10,再用平方与线性还原:Y=var_Y²,X=(var_X+var_Y)/1.02,Z=−(var_Z−var_Y)/0.847。 CIELab 分支更严谨,引用白点 ref_X=95.047、ref_Y=100.0、ref_Z=108.883(D65 标准光源)。当通道比值大于 0.008856 走立方根,否则走 7.787x+16/116 的线性段;最终 L=116·var_Y−16,a=500·(var_X−var_Y),b=200·(var_Y−var_Z)。外汇与贵金属图表配色涉及视觉疲劳判断,属高风险辅助手段,参数错了可能看错信号。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Conversion of XYZ into HunterLab                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CColors::XYZtoHunterLab(class="kw">const class="type">class="kw">double aX,class="kw">const class="type">class="kw">double aY,class="kw">const class="type">class="kw">double aZ,class="type">class="kw">double &oL,class="type">class="kw">double &oa,class="type">class="kw">double &ob)
  {
   oL =class="num">10.0*::sqrt(aY);
   oa =class="num">17.5*(((class="num">1.02*aX)-aY)/::sqrt(aY));
   ob =class="num">7.0*((aY-(class="num">0.847*aZ))/::sqrt(aY));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Conversion of HunterLab into XYZ                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CColors::HunterLabToXYZ(class="kw">const class="type">class="kw">double aL,class="kw">const class="type">class="kw">double aa,class="kw">const class="type">class="kw">double ab,class="type">class="kw">double &oX,class="type">class="kw">double &oY,class="type">class="kw">double &oZ)
  {
   class="type">class="kw">double var_Y =aL/class="num">10.0;
   class="type">class="kw">double var_X =aa/class="num">17.5*aL/class="num">10.0;
   class="type">class="kw">double var_Z =ab/class="num">7.0*aL/class="num">10.0;
class=class="str">"cmt">//---
   oY =::pow(var_Y,class="num">2);
   oX =(var_X+oY)/class="num">1.02;
   oZ =-(var_Z-oY)/class="num">0.847;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Conversion of XYZ into CIELab                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CColors::XYZtoCIELab(class="kw">const class="type">class="kw">double aX,class="kw">const class="type">class="kw">double aY,class="kw">const class="type">class="kw">double aZ,class="type">class="kw">double &oCIEL,class="type">class="kw">double &oCIEa,class="type">class="kw">double &oCIEb)
  {
   class="type">class="kw">double ref_X =class="num">95.047;
   class="type">class="kw">double ref_Y =class="num">100.0;
   class="type">class="kw">double ref_Z =class="num">108.883;
   class="type">class="kw">double var_X =aX/ref_X;
   class="type">class="kw">double var_Y =aY/ref_Y;
   class="type">class="kw">double var_Z =aZ/ref_Z;
class=class="str">"cmt">//---
   if(var_X>class="num">0.008856)
      var_X=::pow(var_X,class="num">1.0/class="num">3.0);
   else
      var_X=(class="num">7.787*var_X)+(class="num">16.0/class="num">116.0);
class=class="str">"cmt">//---
   if(var_Y>class="num">0.008856)
      var_Y=::pow(var_Y,class="num">1.0/class="num">3.0);
   else
      var_Y=(class="num">7.787*var_Y)+(class="num">16.0/class="num">116.0);
class=class="str">"cmt">//---
   if(var_Z>class="num">0.008856)
      var_Z=::pow(var_Z,class="num">1.0/class="num">3.0);
   else
      var_Z=(class="num">7.787*var_Z)+(class="num">16.0/class="num">116.0);
class=class="str">"cmt">//---
   oCIEL =(class="num">116.0*var_Y)-class="num">16.0;
   oCIEa =class="num">500.0*(var_X-var_Y);
   oCIEb =class="num">200*(var_Y-var_Z);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

Lab 与 LCH 互转的参照白与阈值

把 CIELab 转回 XYZ 时,代码里写死了 D65 光源的参照白:ref_X=95.047、ref_Y=100.0、ref_Z=108.883。如果你在 MT5 里做色谱分析却换了光源,这组常数不跟着改就会偏色,外汇报价热力图可能因此失真。 非线性段的判定阈值是 0.008856,对应 f(t) 分段里的 216/24389。大于它走立方,小于它走 (t-16/116)/7.787 的线性近似——这个分界直接决定暗部颜色在指标面板上的渐变是否平滑。 Lab 转 LCH 用 Arctan2(b,a) 算色相,负角会被补成 360° 减绝对值角度。LCH 转回 Lab 时 a=cos(πH/180)*C、b=sin(πH/180)*C(b 行在后续片段),角度单位必须锁死度数,传弧度进去色相直接乱套。 开 MT5 把这几段塞进自定义指标,用已知 RGB 跑一遍 Lab→XYZ→Lab,误差超过 1e-6 就说明参照白或阈值被你动过。

MQL5 / C++
class="type">void CColors::CIELabToXYZ(class="kw">const class="type">class="kw">double aCIEL,class="kw">const class="type">class="kw">double aCIEa,class="kw">const class="type">class="kw">double aCIEb,class="type">class="kw">double &oX,class="type">class="kw">double &oY,class="type">class="kw">double &oZ)
  {
   class="type">class="kw">double var_Y =(aCIEL+class="num">16.0)/class="num">116.0;
   class="type">class="kw">double var_X =aCIEa/class="num">500.0+var_Y;
   class="type">class="kw">double var_Z =var_Y-aCIEb/class="num">200.0;
class=class="str">"cmt">//---
   if(::pow(var_Y,class="num">3)>class="num">0.008856)
      var_Y=::pow(var_Y,class="num">3);
   else
      var_Y=(var_Y-class="num">16.0/class="num">116.0)/class="num">7.787;
class=class="str">"cmt">//---
   if(::pow(var_X,class="num">3)>class="num">0.008856)
      var_X=::pow(var_X,class="num">3);
   else
      var_X=(var_X-class="num">16.0/class="num">116.0)/class="num">7.787;
class=class="str">"cmt">//---
   if(::pow(var_Z,class="num">3)>class="num">0.008856)
      var_Z=::pow(var_Z,class="num">3);
   else
      var_Z=(var_Z-class="num">16.0/class="num">116.0)/class="num">7.787;
class=class="str">"cmt">//---
   class="type">class="kw">double ref_X =class="num">95.047;
   class="type">class="kw">double ref_Y =class="num">100.0;
   class="type">class="kw">double ref_Z =class="num">108.883;
class=class="str">"cmt">//---
   oX =ref_X*var_X;
   oY =ref_Y*var_Y;
   oZ =ref_Z*var_Z;
  }
class="type">void CColors::CIELabToCIELCH(class="kw">const class="type">class="kw">double aCIEL,class="kw">const class="type">class="kw">double aCIEa,class="kw">const class="type">class="kw">double aCIEb,class="type">class="kw">double &oCIEL,class="type">class="kw">double &oCIEC,class="type">class="kw">double &oCIEH)
  {
   class="type">class="kw">double var_H=Arctan2(aCIEb,aCIEa);
class=class="str">"cmt">//---
   if(var_H>class="num">0)
      var_H=(var_H/M_PI)*class="num">180.0;
   else
      var_H=class="num">360.0-(::fabs(var_H)/M_PI)*class="num">180.0;
class=class="str">"cmt">//---
   oCIEL =aCIEL;
   oCIEC =::sqrt(::pow(aCIEa,class="num">2)+::pow(aCIEb,class="num">2));
   oCIEH =var_H;
  }
class="type">void CColors::CIELCHtoCIELab(class="kw">const class="type">class="kw">double aCIEL,class="kw">const class="type">class="kw">double aCIEC,class="kw">const class="type">class="kw">double aCIEH,class="type">class="kw">double &oCIEL,class="type">class="kw">double &oCIEa,class="type">class="kw">double &oCIEb)
  {
class=class="str">"cmt">//--- Arguments from class="num">0 to class="num">360°
   oCIEL =aCIEL;
   oCIEa =::cos(M_PI*aCIEH/class="num">180.0)*aCIEC;

「把颜色在 CIELuv 和 XYZ 之间来回倒」

CIELuv 的优势是人眼对明暗和彩度变化更线性,但 MT5 画图底层还是吃 XYZ 三刺激值,所以得有两套互转函数兜底。下面这段直接挂进你自己的 CColors 类就能用,不用改接口。 XYZtoCIELuv 里先算 var_U、var_V 两个归一化坐标,分母都是 X+15Y+3Z;var_Y 走分段:大于 0.008856 就开三次方,否则用 7.787 斜率近似。参考白点写死 ref_X=95.047、ref_Y=100.000、ref_Z=108.883(D65 标准光源),最后 L 用 116*var_Y-16,u/v 再乘 13*L 拉偏。 反向 CIELuvToXYZ 先用 (L+16)/116 还原 var_Y,同样按 0.008856 切真假立方根;ref_U/ref_V 跟正向一致,var_U、var_V 靠 u/(13L)+ref_U 找回。最后 Y=var_Y*100,X 和 Z 用那两条带负号和分母交叉项的式子推出,跑出来若 X 接近 0 说明原色偏蓝紫。 开 MT5 新建脚本随便喂一组 L=50、u=0、v=0 进去,回推的 XYZ 应该 Y≈18.4、X/Z 比例贴着参考白点,不对就是分母 var_V 漏了括号。

MQL5 / C++
class="type">void CColors::XYZtoCIELuv(class="kw">const class="type">class="kw">double aX,class="kw">const class="type">class="kw">double aY,class="kw">const class="type">class="kw">double aZ,class="type">class="kw">double &oCIEL,class="type">class="kw">double &oCIEu,class="type">class="kw">double &oCIEv)
  {
   class="type">class="kw">double var_U =(class="num">4.0*aX)/(aX+(class="num">15.0*aY)+(class="num">3.0*aZ));
   class="type">class="kw">double var_V =(class="num">9.0*aY)/(aX+(class="num">15.0*aY)+(class="num">3.0*aZ));
   class="type">class="kw">double var_Y =aY/class="num">100.0;
class=class="str">"cmt">//---
   if(var_Y>class="num">0.008856)
      var_Y=::pow(var_Y,class="num">1.0/class="num">3.0);
   else
      var_Y=(class="num">7.787*var_Y)+(class="num">16.0/class="num">116.0);
class=class="str">"cmt">//---
   class="type">class="kw">double ref_X =class="num">95.047;
   class="type">class="kw">double ref_Y =class="num">100.000;
   class="type">class="kw">double ref_Z =class="num">108.883;
   class="type">class="kw">double ref_U =(class="num">4.0*ref_X)/(ref_X+(class="num">15.0*ref_Y)+(class="num">3.0*ref_Z));
   class="type">class="kw">double ref_V =(class="num">9.0*ref_Y)/(ref_X+(class="num">15.0*ref_Y)+(class="num">3.0*ref_Z));
class=class="str">"cmt">//---
   oCIEL =(class="num">116.0*var_Y)-class="num">16.0;
   oCIEu =class="num">13.0*oCIEL*(var_U-ref_U);
   oCIEv =class="num">13.0*oCIEL*(var_V-ref_V);
  }
class="type">void CColors::CIELuvToXYZ(class="kw">const class="type">class="kw">double aCIEL,class="kw">const class="type">class="kw">double aCIEu,class="kw">const class="type">class="kw">double aCIEv,class="type">class="kw">double &oX,class="type">class="kw">double &oY,class="type">class="kw">double &oZ)
  {
   class="type">class="kw">double var_Y=(aCIEL+class="num">16.0)/class="num">116.0;
class=class="str">"cmt">//---
   if(::pow(var_Y,class="num">3)>class="num">0.008856)
      var_Y=::pow(var_Y,class="num">3);
   else
      var_Y=(var_Y-class="num">16.0/class="num">116.0)/class="num">7.787;
class=class="str">"cmt">//---
   class="type">class="kw">double ref_X =class="num">95.047;
   class="type">class="kw">double ref_Y =class="num">100.000;
   class="type">class="kw">double ref_Z =class="num">108.883;
   class="type">class="kw">double ref_U =(class="num">4.0*ref_X)/(ref_X+(class="num">15.0*ref_Y)+(class="num">3.0*ref_Z));
   class="type">class="kw">double ref_V =(class="num">9.0*ref_Y)/(ref_X+(class="num">15.0*ref_Y)+(class="num">3.0*ref_Z));
   class="type">class="kw">double var_U =aCIEu/(class="num">13.0*aCIEL)+ref_U;
   class="type">class="kw">double var_V =aCIEv/(class="num">13.0*aCIEL)+ref_V;
class=class="str">"cmt">//---
   oY=var_Y*class="num">100.0;
   oX=-(class="num">9.0*oY*var_U)/((var_U-class="num">4.0)*var_V-var_U*var_V);
   oZ=(class="num">9.0*oY-(class="num">15.0*var_V*oY)-(var_V*oX))/(class="num">3.0*var_V);
  }
把绘图底层检查交给小布
这些画布清屏与文本锚点的诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到调用是否合理,你专注上层逻辑。

常见问题

零值只清 RGB 不留 alpha 通道痕迹,需用 0x00FFFFFF 这种 ARGB 全透黑才能连透明通道一起擦干净,否则叠加绘制容易留影。
水平用 TA_LEFT、TA_CENTER、TA_RIGHT,垂直用 TA_TOP 等标志两两组合,决定文本相对边界矩形的对齐定位点。
自定义 GUI 在切时间帧时应记住位置状态,先预留存读文件的方法,等集合类开发完就能直接复用,不必返工。
可以,小布内置的 AIGC 看盘页能提示画布清理与文本绘制是否合理,减少手动翻代码的重复劳动。
取自 Dmitry Fedoseev 提交到社区代码库的实现,本篇把它纳入函数库统一用于图形元素颜色操控。