記事「MQL5取引ツール(第17回):ベクトルベースの角丸長方形と三角形を探る」についてのディスカッション

 

新しい記事「MQL5取引ツール(第17回):ベクトルベースの角丸長方形と三角形を探る」はパブリッシュされました:

MQL5のcanvasを使用して、ベクトルベースの手法で角丸長方形と三角形を描画する方法を解説します。さらに、アンチエイリアス処理を実現するためにスーパーサンプリングを利用します。実装では、スキャンライン塗りつぶし、円弧や接線に対する幾何学的事前計算、そして境界線描画を組み合わせることで、滑らかでカスタマイズ可能な図形を作成します。このアプローチは、今後の取引ツールにおけるモダンなUI要素を構築するための基盤となり、サイズ、角の半径、境界線、透明度などを入力で柔軟に設定できる設計になっています。

ベクトルベースによる角丸長方形と三角形の描画では、ピクセル単位のグリッドではなく、点、線、曲線などの数学的な図形表現を使用します。これにより、どのようなサイズでも鮮明さを維持できる、拡大縮小に依存しないグラフィックを実現できます。ラスタ方式では、拡大縮小時にエッジがギザギザになるエイリアシングが発生することがあります。一方、ベクトル方式では、円弧や接線の方程式を利用して正確な境界や塗りつぶし領域を計算します。このため、滑らかな表示によって操作性が向上するMQL5のUI要素に適しています。角丸は、鋭い頂点を円弧へ置き換えることで実現されます。この円弧の半径によって、曲率が制御されます。境界線については、オフセットされたパスや太さを持たせたエッジを使用して表現します。さらに、スーパーサンプリングによって高解像度で一度描画してから縮小処理をおこなうことで、不要な描画アーティファクトを除去し、さらに滑らかな結果を得られます。

今回の実装では、スーパーサンプリングを利用した高解像度canvasを構築し、三角形の円弧や接線に必要なジオメトリ計算を事前におこないます。また、両方の図形に対してスキャンライン塗りつぶしを使用し、内部領域を正確に描画します。さらに、ベクトルベースの辺と角部の円弧を利用して、カスタマイズ可能な境界線を追加します。サイズ、角の半径、透明度、色などのユーザー入力にも対応し、現代的なトレーディングインターフェースに適した柔軟でアンチエイリアス処理された図形を作成します。簡単にまとめると、今回の目的を視覚的に表現したものが以下の図になります。


作者: Allan Munene Mutiiria

 
この記事、ありがとうございます!とても参考になり、詳しく説明されています。
角丸長方形にベベルエッジ効果(3Dライティング――上・左が白、下・右が黒)を追加しようとしています。
直線のエッジにはベベル効果をうまく実装できたのですが、角でそれらをつなぐのに苦労しています。
下の画像で、線が交差している部分に赤い丸で印をつけた箇所をご覧ください。
何かアドバイスはありますか?


//+------------------------------------------------------------------+
//| 面取りエフェクト
//+------------------------------------------------------------------+
void DrawBeveledEdges(int posX, int posY, int width, int height, int radius, int scaleFactor)
  {
   color lightColor = clrWhite;
   uint lightARGB = ColorToARGBWithOpacity(lightColor, 20);

   color darkColor = clrBlack;
   uint darkARGB = ColorToARGBWithOpacity(darkColor, 35);

   int bevelWidth = 3 * scaleFactor;

// ===== 明るい面(左上) =====

// 上部の水平部分
   for(int y = 0; y < bevelWidth; y++)
     {
      for(int x = radius; x < width - radius; x++)
        {
         rectangleHighResCanvas.PixelSet(posX + x, posY + y, lightARGB);
        }
     }

// 左側の縦部分
   for(int x = 0; x < bevelWidth; x++)
     {
      for(int y = radius; y < height - radius; y++)
        {
         rectangleHighResCanvas.PixelSet(posX + x, posY + y, lightARGB);
        }
     }

// 左上のARC - 明るい色のみ
   for(int dy = -radius; dy <= 0; dy++)
     {
      for(int dx = -radius; dx <= 0; dx++)
        {
         double dist = MathSqrt((double)(dx*dx + dy*dy));

         if(dist >= radius - bevelWidth && dist <= radius + 1)
           {
            int pixX = posX + radius + dx;
            int pixY = posY + radius + dy;

            if(pixX >= posX && pixY >= posY)
               rectangleHighResCanvas.PixelSet(pixX, pixY, lightARGB);
           }
        }
     }

// 右上のARC - 明るい色のみ(右上隅)
   for(int dy = -radius; dy <= 0; dy++)
     {
      for(int dx = 0; dx <= radius; dx++)
        {
         double dist = MathSqrt((double)(dx*dx + dy*dy));

         if(dist >= radius - bevelWidth && dist <= radius + 1)
           {
            int pixX = posX + width - radius + dx;
            int pixY = posY + radius + dy;

            if(pixX < posX + width && pixY >= posY)
               rectangleHighResCanvas.PixelSet(pixX, pixY, lightARGB);
           }
        }
     }

// ===== ダークエッジ(右下) =====

// 下部の水平部分
   for(int y = height - bevelWidth; y < height; y++)
     {
      for(int x = radius; x < width - radius; x++)
        {
         rectangleHighResCanvas.PixelSet(posX + x, posY + y, darkARGB);
        }
     }

// 右側の縦部分
   for(int x = width - bevelWidth; x < width; x++)
     {
      for(int y = radius; y < height - radius; y++)
        {
         rectangleHighResCanvas.PixelSet(posX + x, posY + y, darkARGB);
        }
     }

// 右下のARC - 濃い色のみ
   for(int dy = 0; dy <= radius; dy++)
     {
      for(int dx = 0; dx <= radius; dx++)
        {
         double dist = MathSqrt((double)(dx*dx + dy*dy));

         if(dist >= radius - bevelWidth && dist <= radius + 1)
           {
            int pixX = posX + width - radius + dx;
            int pixY = posY + height - radius + dy;

            if(pixX < posX + width && pixY < posY + height)
               rectangleHighResCanvas.PixelSet(pixX, pixY, darkARGB);
           }
        }
     }

// 左下のARC - 暗い色のみ(左下隅)
   for(int dy = 0; dy <= radius; dy++)
     {
      for(int dx = -radius; dx <= 0; dx++)
        {
         double dist = MathSqrt((double)(dx*dx + dy*dy));

         if(dist >= radius - bevelWidth && dist <= radius + 1)
           {
            int pixX = posX + radius + dx;
            int pixY = posY + height - radius + dy;

            if(pixX >= posX && pixY < posY + height)
               rectangleHighResCanvas.PixelSet(pixX, pixY, darkARGB);
           }
        }
     }
  }
//+------------------------------------------------------------------+


追伸 -今後の記事の アイデア:
ドロップシャドウとグロー効果 - UI要素に奥行きを加える方法
グラデーション塗り - 色の変化で3Dのような外観を作る
アンチエイリアシングの手法 - プロフェッショナルな仕上がりのためのエッジの滑らか化
インナーシャドウ - 3Dベベル効果の強化
テクスチャとノイズパターン - 平面的な形状にリアリティを加える