English 中文 Español Deutsch 日本語 Português
Изучаем класс CCanvas. Сглаживание и тени

Изучаем класс CCanvas. Сглаживание и тени

MetaTrader 5Примеры | 4 декабря 2015, 10:50
3 710 10
Vladimir Karputov
Vladimir Karputov

Оглавление

 

Введение

Думаю, что одной из задач, которые решаются при рисовании с помощью класса CCanvas — это отображение различных динамических эффектов. Например, реализация графических построений с применением сглаживания — такие сглаженные построения приобретают более привлекательный вид. Или рисование нового стиля отображения линии индикатора — сплайн. А может, это будет рисование динамического индикатора в отдельном окне — что-то похожее на рисование частотной характеристики на осциллографе. В любом случае рисование открывает новые горизонты применения в своих разработках.

 

1. Координаты и канвас

Канвас строится в координатах графика. Размер графика в этом случае измеряется в пикселях. Левый верхний угол графика — это координаты (0,0).

При рисовании на канвасе следует иметь в виду, что координаты примитивов и закрашенных примитивов задаются исключительно в int. А вот при рисовании примитивов с использованием сглаживания методом PixelSetAA координаты задаются в double, в методе CircleAA координаты задаются в int, а размер окружности в double.

Метод Координаты Размер
PixelSetAA double -
LineAA int -
PolylineAA int -
PolygonAA int -
TriangleAA int -
CircleAA int double

 

То есть при задании координат для метода PixelSetAA координаты точки могут быть вида: (120.3, 25.56). Скрипт PixelSetAA.mq5 рисует два столбца по одиннадцать точек. В левом столбце приращение для каждой точки по оси X составляет 0.1, а приращение по оси Y — 3.0. В правом столбце приращение для каждой точки по оси X составляет 0.1, а приращение по оси Y — 3.1.

Чтобы вы могли увидеть, каким образом будут нарисованы эти точки, результат работы скрипта PixelSetAA.mq5 был многократно увеличен:

Рис. 1. Работа метода PixelSetAA

Рис. 1. Работа метода PixelSetAA

Для наглядности работы скрипта я добавил границы, в которых происходит сглаживание и текст с координатами для рисования:

Рис. 2. Наглядная работа метода PixelSetAA

Рис. 2. Наглядная работа метода PixelSetAA

Как видите, только в координатах без дробной части пиксель закрашивается заданным цветом. Если же у точки одна из координат задается с дробной частью, то такая точка будет рисоваться двумя пикселями с разной цветовой насыщенностью (левый столбец).

В случае, когда обе координаты точки заданы с дробной частью, такая точка рисуется уже тремя пикселями с разной цветовой насыщенностью (правый столбец). Именно такое рисование тремя пикселями, но с разной цветовой насыщенностью, позволяет получить эффект сглаженности.

 

2. Алгоритм сглаживания

Методы класса CCanvas, рисующие примитивы с использованием сглаживания, для отображения на экране используют общий метод расчета цвета точки — PixelSetAA.

Метод Конечный метод расчета изображения
PixelSetAA PixelSetAA
LineAA PixelSetAA
PolylineAA LineAA -> PixelSetAA
PolygonAA LineAA -> PixelSetAA
TriangleAA LineAA -> PixelSetAA
CircleAA PixelSetAA

Демонстрация метода рисования со сглаживанием PixelSetAA была видна на рис. 1.

Получается, что при рисовании со сглаживанием метод PixelSetAA — это основа класса CCanvas. Поэтому, думаю, будет интересно разобраться, а как именно реализуется алгоритм сглаживания.

Напомню, что координаты X и Y метода PixelSetAA имеют тип double, таким образом, метод PixelSetAA может принимать координаты точки, расположенной между пикселями:

//+------------------------------------------------------------------+
//| Draw pixel with antialiasing                                     |
//+------------------------------------------------------------------+
void CCanvas::PixelSetAA(const double x,const double y,const uint clr)
  {

Дальше объявляем три массива. Массив rr[] — вспомогательный массив для вычисления, насколько виртуальный пиксель (пиксель который мы хотим нарисовать) перекрывает физические пиксели экрана. Массивы xx[] и yy[] — это массивы координат, по которым будут рисоваться пиксели для придания изображению эффекта сглаженности.

void CCanvas::PixelSetAA(const double x,const double y,const uint clr)
  {
   static double rr[4];
   static int    xx[4];
   static int    yy[4];

Рисунок ниже продемонстрирует связь между виртуальным пикселем и перекрытием физических пикселей:

Перекрытие физических пикселей

Рис. 3. Перекрытие физических пикселей

То есть виртуальный пиксель (пиксель, координаты которого мы получили расчетным путем) часто имеет дробные координаты и может одновременно частично перекрывать четыре физических пикселя. В этом случае алгоритм сглаживания должен выполнить свою главную задачу — закрасить эти четыре физических пикселя цветом виртуального пикселя, но с разной интенсивностью. И таким образом обмануть наше зрение — глаз будет видеть немного размытое изображение, но без резких цветовых переходов и с плавными границами.

Следующий блок — предварительные вычисления. Получаем значения входящих координат, округленные до ближайшего целого:

static int    yy[4];
//--- preliminary calculations
   int    ix=(int)MathRound(x);
   int    iy=(int)MathRound(y);

Для понимания, как работает математическая функция MathRound (в какую сторону будет округление, если число имеет дробную часть ".5") лучше запустить такой код:

void OnStart()
  {
   Print("MathRound(3.2)=",DoubleToString(MathRound(3.2),8),"; (int)MathRound(3.2)=",IntegerToString((int)MathRound(3.2)));
   Print("MathRound(3.5)=",DoubleToString(MathRound(3.5),8),"; (int)MathRound(3.5)=",IntegerToString((int)MathRound(3.5)));
   Print("MathRound(3.8)=",DoubleToString(MathRound(3.8),8),"; (int)MathRound(3.8)=",IntegerToString((int)MathRound(3.8)));
  }
//+------------------------------------------------------------------+

и результат выполнения:

MathRound(3.8)=4.00000000; (int)MathRound(3.8)=4
MathRound(3.5)=4.00000000; (int)MathRound(3.5)=4
MathRound(3.2)=3.00000000; (int)MathRound(3.2)=3

Дальше идет вычисление дельты dx и dy — разности между входящими координатами x и y и округленными значениями ix и iy:

int    iy=(int)MathRound(y);
   double rrr=0;
   double k;
   double dx=x-ix;
   double dy=y-iy;

Теперь проверяем: если и дельта dx, и дельта dy равны нулю, то покидаем метод PixelSetAA.

double dy=y-iy;
   uchar  a,r,g,b;
   uint   c;
//--- no need for anti-aliasing
   if(dx==0.0 && dy==0.0)
     {
      PixelSet(ix,iy,clr);
      return;
     }

Если дельты оказались не равны нулю, то приступим к подготовке массива пикселей:

PixelSet(ix,iy,clr);
      return;
     }
//--- prepare array of pixels
   xx[0]=xx[2]=ix;
   yy[0]=yy[1]=iy;
   if(dx<0.0)
      xx[1]=xx[3]=ix-1;
   if(dx==0.0)
      xx[1]=xx[3]=ix;
   if(dx>0.0)
      xx[1]=xx[3]=ix+1;
   if(dy<0.0)
      yy[2]=yy[2]=iy-1;
   if(dy==0.0)
      yy[2]=yy[2]=iy;
   if(dy>0.0)
      yy[2]=yy[2]=iy+1;

Именно этот блок создает основу для создания иллюзии сглаженного изображения.

Для визуализации работы этого блока я написал скрипт PrepareArrayPixels.mq5 и видео работы этого скрипта:

Видео 1. Работа скрипта PrepareArrayPixels.mq5

После заполнения массива пикселей идет вычисление "весов" — насколько виртуальный пиксель перекрывает реальные пиксели:

yy[2]=yy[2]=iy+1;
//--- calculate radii and sum of their squares
   for(int i=0;i<4;i++)
     {
      dx=xx[i]-x;
      dy=yy[i]-y;
      rr[i]=1/(dx*dx+dy*dy);
      rrr+=rr[i];
     }

И заключительный этап — рисование размытия:

rrr+=rr[i];
     }
//--- draw pixels
   for(int i=0;i<4;i++)
     {
      k=rr[i]/rrr;
      c=PixelGet(xx[i],yy[i]);
      a=(uchar)(k*GETRGBA(clr)+(1-k)*GETRGBA(c));
      r=(uchar)(k*GETRGBR(clr)+(1-k)*GETRGBR(c));
      g=(uchar)(k*GETRGBG(clr)+(1-k)*GETRGBG(c));
      b=(uchar)(k*GETRGBB(clr)+(1-k)*GETRGBB(c));
      PixelSet(xx[i],yy[i],ARGB(a,r,g,b));
     }

 

3. Тень объекта

Рисование тени придает графическим объектам более мягкие очертания контура. При этом возникает небольшой эффект объема — графические объекты визуально перестают быть плоскими. К тому же тени обладают очень интересным и полезным свойством — тени объектов, как правило, полупрозрачны, и при наложении графики с тенями создается дополнительное ощущение объема.


3.1. Виды тени

Ниже представлены самые распространенные виды тени:

Рис. 4. Виды теней

Рис. 4. Виды теней

Тень вида "ореол" может иметь настройку — ширина ореола. Тень вида "снаружи по диагонали" имеет настройку, в какой угол смещается тень. Оба вида тени имеют настройку выбора цвета тени.

Чтобы выбрать подходящий алгоритм рисования тени, нам нужно увидеть, а из чего состоит тень? В этом нам поможет сильное увеличение. Вот как выглядят тени с рисунка 4 при сильном увеличении:

Рис. 5. Из чего состоит тень.

Рис. 5. Из чего состоит тень

Как теперь видно, тень "ореол" строится из нескольких контуров толщиной в 1 пиксель. У этих контуров плавно изменяется насыщенность цвета.


3.2. Получение нормального распределения

Для получения плавного перехода при рисовании тени будем использовать самый распространенный графический фильтр — размытие по Гауссу (про алгоритм размытия по Гауссу будет ниже). Данный фильтр использует нормальное распределение при вычислении преобразований, применяемых к каждому пикселю изображения. Расчет размытия каждого пикселя изображения зависит от радиуса размытия (этот параметр задается перед началом применения фильтра) и должен проводиться с учетом всех окружающих пикселей.

Хотя говорят о радиусе размытия, но на самом деле в расчетах применяют решетку из пикселей размером N x N:

формула решетки

где Radius — это радиус размытия.

На рисунке ниже приведен пример решетки пикселей для радиуса размытия, равному 3.

Рис. 6. Радиус размытия.

Рис. 6. Радиус размытия

Здесь я опускаю теорию ускорения расчетов данного фильтра, скажу только, что мы будем использовать свойство сепарабельности Гауссова фильтра: сначала применяем размытие по оси X, а затем применяем размытие по оси Y. В таком случае получается ускорение расчетов, а качество при этом не страдает.

Влияние соседних пикселей на расчетный пиксель неодинаково и рассчитывается по нормальному распределению. Чем дальше находится пиксель от расчетного, тем меньше его влияние на расчетный пиксель. Для расчета нормального распределения по Гауссу будем использовать библиотеку численного анализа ALGLIB. Наглядно продемонстрировать моделирование нормального распределения поможет скрипт GQGenerateRecToExel.mq5. Этот скрипт получает, при помощи библиотеки ALGLIB, массив весовых коэффициентов нормального распределения и выводит эти значения в файл <каталог данных>\MQL5\Files\GQGenerateRecToExel.csv. И вот как выглядит график, построенный на основе данных файла GQGenerateRecToExel.csv:

Рис. 7. Нормальное распределение

Рис. 7. Нормальное распределение

На примере скрипта GQGenerateRecToExel.mq5 рассмотрим пример получения массива весовых коэффициентов нормального распределения. Здесь и далее в скриптах будет использоваться одна и та же функция GetQuadratureWeights:

//+------------------------------------------------------------------+
//| Gets array of quadrature weights                                 |
//+------------------------------------------------------------------+
bool GetQuadratureWeights(const double mu0,const int n,double &w[])
  {
   CAlglib alglib;            // static member of class CAlglib
   double      alp[];         // array alpha coefficients 
   double      bet[];         // array beta coefficients 
   ArrayResize(alp,n);
   ArrayResize(bet,n);
   ArrayInitialize(alp,1.0);  // initializes a numeric array alpha
   ArrayInitialize(bet,1.0);  // initializes a numeric array beta

   double      out_x[];
   int         inf=0;
//| Info    -   error code:                                          |
//|                 * -3    internal eigenproblem solver hasn't      |
//|                         converged                                |
//|                 * -2    Beta[i]<=0                               |
//|                 * -1    incorrect N was passed                   |
//|                 *  1    OK                                       |
   alglib.GQGenerateRec(alp,bet,mu0,n,inf,out_x,w);
   if(inf!=1)
     {
      Print("Call error in CGaussQ::GQGenerateRec");
      return(false);
     }
   return(true);
  }

Данная функция заполняет массив w[] весовыми коэффициентами нормального распределения, а также проверяет результат вызова функции библиотеки ALGLIB через анализ переменной inf.


3.3. Ресурсы

При рисовании тени на канвасе используются операции с ресурсами — ResourceReadImage — чтение данных из графического ресурса и заполнение этими данными массива.

При работе с ресурсами следует обратить внимание на то, что массивы пикселей сохраняются в формате uint (подробнее: ARGB-представление цвета). Также нужно знать, как двумерная картинка, которая имеет ширину и высоту, преобразовывается в одномерный массив. Алгоритм преобразования следующий: идет последовательная склейка строчек картинки в одну большую строчку. На рисунке ниже показаны две картинки размерами 4 x 3 пикселя и 3 x 4 пикселя, которые преобразуются в одномерный массив:

Рис. 8. Преобразование картинки в одномерный массив

Рис. 8. Преобразование картинки в одномерный массив

 

4. Пример алгоритма размытия по Гауссу

Размытие по Гауссу рассмотрим на примере алгоритма ShadowTwoLayers.mq5. Для работы скрипта нужны два подключаемых файла: Canvas.mqh и библиотека численного анализа ALGLIB:

#property script_show_inputs
#include <Canvas\Canvas.mqh>
#include <Math\Alglib\alglib.mqh>

Входные параметры:

//--- input
input uint  radius=4;               // radius blur
input color clrShadow=clrBlack;     // shadow color
input uchar ShadowTransparence=160; // transparency shadows
input int   ShadowShift=3;          // shadow shift
input color clrDraw=clrBlue;        // shadow color
input uchar DrawwTransparence=255;  // transparency draws
//---

Для работы создадим два канваса. Нижний канвас будет выполнять функцию слоя, на котором рисуется тень, а верхний канвас будет выполнять функцию рабочего слоя — на нем будем рисовать графические фигуры. Размеры обоих канвасов равны размеру графика (описание функций получения высоты и ширины графика в пикселях я не описываю, так как эти примеры есть в документации в разделе Примеры работы с графиком):

//--- create canvas
   CCanvas CanvasShadow;
   CCanvas CanvasDraw;
   if(!CanvasShadow.CreateBitmapLabel("ShadowLayer",0,0,ChartWidth,
      ChartHeight,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      Print("Error creating canvas: ",GetLastError());
      return;
     }
   if(!CanvasDraw.CreateBitmapLabel("DrawLayer",0,0,ChartWidth
      ,ChartHeight,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      Print("Error creating canvas: ",GetLastError());
      return;
     }

Теперь немного рисования на канвасе. Сначала рисуем на нижнем канвасе заготовки фигур тени (по умолчанию тени рисуются полупрозрачными), а затем рисуем на верхнем канвасе прямоугольник.

//--- draw on canvas
   CanvasShadow.Erase(ColorToARGB(clrNONE,0));
   CanvasShadow.FillRectangle(ChartWidth/10,ChartHeight/10,
                              ChartWidth/2-ChartWidth/10,ChartHeight/10*9,ColorToARGB(clrShadow,ShadowTransparence));
   CanvasShadow.FillRectangle(ChartWidth/2,ChartHeight/12,ChartWidth/3*2,
                              ChartHeight/2,ColorToARGB(clrShadow,ShadowTransparence));
   CanvasShadow.Update();

   CanvasDraw.Erase(ColorToARGB(clrNONE,0));
   CanvasDraw.FillRectangle(ChartWidth/10-ShadowShift,ChartHeight/10-ShadowShift,ChartWidth/2-ChartWidth/10-ShadowShift,
                            ChartHeight/10*9-ShadowShift,ColorToARGB(clrDraw,DrawwTransparence));
   CanvasDraw.Update();

Должен выйти вот такой рисунок (обратите внимание: прямоугольники "тени" еще не имеют размытия):

Рис. 9. Тени еще не размыты

Рис. 9. Тени еще не размыты

Размытие будет выполняться на нижнем канвасе (CanvasShadow). Для этого нужно прочитать данные (ResourceReadImage) из графического ресурса нижнего канваса (CanvasShadow.ResourceName()) и заполнить этими данными одномерный массив (res_data):

//+------------------------------------------------------------------+
//| reads data from the graphical resource                           |
//+------------------------------------------------------------------+
   ResetLastError();
   if(!ResourceReadImage(CanvasShadow.ResourceName(),res_data,res_width,res_height))
     {
      Print("Error reading data from the graphical resource ",GetLastError());
      Print("attempt number two");
      //--- attempt number two: now the picture width and height are known
      ResetLastError();
      if(!ResourceReadImage(CanvasShadow.ResourceName(),res_data,res_width,res_height))
        {
         Print("Error reading data from the graphical resource ",GetLastError());
         return;
        }
     }

Следующий шаг — это получение массива весовых коэффициентов нормального распределения через вызов функции GetQuadratureWeights и разложение одномерного массива на четыре массива: Alfa, Red, Green, Blue (альфа-канал, красный, зеленый, голубой). Разложение по цветам необходимо потому, что графические эффекты нужно применять для каждого компонента цвета.

//+------------------------------------------------------------------+
//| decomposition of pictures on the components r, g, b              |
//+------------------------------------------------------------------+
...
   if(!GetQuadratureWeights(1,NNodes,weights))
      return;

   for(int i=0;i<size;i++)
     {
      clr_temp=res_data[i];
      a_data[i]=GETRGBA(clr_temp);
      r_data[i]=GETRGBR(clr_temp);
      g_data[i]=GETRGBG(clr_temp);
      b_data[i]=GETRGBB(clr_temp);
     }

Следующий участок кода отвечает за "магию" — размытие. Сначала размываем изображение по оси X, затем размываем изображение по оси Y. Такой подход вытекает из свойства сепарабельности Гауссова фильтра, при этом получается ускорение расчетов, а качество при этом не страдает. Рассмотрим пример формирования размытия по одной оси картинки — по оси X:

//+------------------------------------------------------------------+
//| blur horizontal (axis X)                                         |
//+------------------------------------------------------------------+
   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;
   for(uint Y=0;Y<res_height;Y++)                  // cycle on image width
     {
      for(uint X=radius;X<res_width-radius;X++)    // cycle on image height
        {
         XY=Y*res_width+X;
         a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0;
         coef=0;
         for(int i=-1*j;i<j+1;i=i+1)
           {
            a_temp+=a_data[XY+i]*weights[coef];
            r_temp+=r_data[XY+i]*weights[coef];
            g_temp+=g_data[XY+i]*weights[coef];
            b_temp+=b_data[XY+i]*weights[coef];
            coef++;
           }
         a_data[XY]=(uchar)MathRound(a_temp);
         r_data[XY]=(uchar)MathRound(r_temp);
         g_data[XY]=(uchar)MathRound(g_temp);
         b_data[XY]=(uchar)MathRound(b_temp);
        }
      //--- remove artifacts on the left
      for(uint x=0;x<radius;x++)
        {
         XY=Y*res_width+x;
         a_data[XY]=a_data[Y*res_width+radius];
         r_data[XY]=r_data[Y*res_width+radius];
         g_data[XY]=g_data[Y*res_width+radius];
         b_data[XY]=b_data[Y*res_width+radius];
        }
      //--- remove artifacts on the right
      for(uint x=res_width-radius;x<res_width;x++)
        {
         XY=Y*res_width+x;
         a_data[XY]=a_data[(Y+1)*res_width-radius-1];
         r_data[XY]=r_data[(Y+1)*res_width-radius-1];
         g_data[XY]=g_data[(Y+1)*res_width-radius-1];
         b_data[XY]=b_data[(Y+1)*res_width-radius-1];
        }
     }

Итак, видим два вложенных цикла:

for(uint Y=0;Y<res_height;Y++)                  // cycle on image width
     {
      for(uint X=radius;X<res_width-radius;X++)    // cycle on image height
        {
         ...
        }
     }

Такое вложение обеспечивает проход по каждому пикселю изображения:

Рис. 10. Обход изображения по каждому пикселю

Рис. 10. Обход изображения по каждому пикселю

Вложенный цикл обеспечивает расчет размытия по оси X для каждого пикселя:

for(uint X=radius;X<res_width-radius;X++)    // cycle on image height
        {
         XY=Y*res_width+X;
         a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0;
         coef=0;
         for(int i=-1*j;i<j+1;i=i+1)
           {
            a_temp+=a_data[XY+i]*weights[coef];
            r_temp+=r_data[XY+i]*weights[coef];
            g_temp+=g_data[XY+i]*weights[coef];
            b_temp+=b_data[XY+i]*weights[coef];
            coef++;
           }
         a_data[XY]=(uchar)MathRound(a_temp);
         r_data[XY]=(uchar)MathRound(r_temp);
         g_data[XY]=(uchar)MathRound(g_temp);
         b_data[XY]=(uchar)MathRound(b_temp);
        }

Для каждого пикселя слева и справа выбирается количество прилегающих пикселей, равное радиусу размытия. Напомню, что ранее мы получили массив весовых коэффициентов нормального распределения через вызов функции GetQuadratureWeights. Получается следующее соответствие: количество прилегающих пикселей слева + пиксель, для которого выполняется расчет размытия + количество прилегающих пикселей справа = количеству элементов массива весовых коэффициентов. Таким образом, каждый соседний пиксель соответствует определенному значению в массиве весовых коэффициентов.

Размытие для каждого цвета считается так: каждый прилегающий пиксель умножается на соответствующий ему весовой коэффициент и полученные значения суммируются. Ниже приведен пример расчета размытия изображения в красном цвете при заданном радиусе размытия, равном 4:

Рис. 11. Расчет размытия

Рис. 11. Расчет размытия

При таком алгоритме размытия по краям изображения остаются артефакты — полосы неразмытых пикселей. Ширина этих полосок равна радиусу размытия. Чем больше радиус размытия, тем шире полосы из неразмытых пикселей. В данном алгоритме такие артефакты устраняются копированием размытых пикселей:

//--- remove artifacts on the left
      for(uint x=0;x<radius;x++)
        {
         XY=Y*res_width+x;
         a_data[XY]=a_data[Y*res_width+radius];
         r_data[XY]=r_data[Y*res_width+radius];
         g_data[XY]=g_data[Y*res_width+radius];
         b_data[XY]=b_data[Y*res_width+radius];
        }
      //--- remove artifacts on the right
      for(uint x=res_width-radius;x<res_width;x++)
        {
         XY=Y*res_width+x;
         a_data[XY]=a_data[(Y+1)*res_width-radius-1];
         r_data[XY]=r_data[(Y+1)*res_width-radius-1];
         g_data[XY]=g_data[(Y+1)*res_width-radius-1];
         b_data[XY]=b_data[(Y+1)*res_width-radius-1];
        }

Аналогичные операции по размытию проводим для оси Y. В результате получаем четыре массива a1_data[], r1_data[], g1_data[], b1_data[], в которых записаны размытые значения для альфа-канала, красного, зеленого и голубого соответственно. Остается из этих четырех компонентов собрать цвет для каждого пикселя и применить к канвасу CanvasShadow:

//---
   for(int i=0;i<size;i++)
     {
      clr_temp=ARGB(a1_data[i],r1_data[i],g1_data[i],b1_data[i]);
      res_data[i]=clr_temp;
     }
   for(uint X=0;X<res_width;X++)
     {
      for(uint Y=radius;Y<res_height-radius;Y++)
        {
         XY=Y*res_width+X;
         CanvasShadow.PixelSet(X,Y,res_data[XY]);
        }
     }
   CanvasShadow.Update();
   CanvasDraw.Update();
   Sleep(21000);

Результат размытия слоя с тенями:

Рис. 12. Тени теперь размыты

Рис. 12. Тени теперь размыты

 

5. Класс, рисующий тень

Пример рисования на канвасе реализуем в классе CGauss. Класс CGauss позволяет рисовать такие примитивы с тенью:

Примитивы Описание
LineVertical Рисует вертикальную линию с тенью
LineHorizontal Рисует горизонтальную линию с тенью
Line Рисует произвольную линию с тенью
Polyline Рисует ломаную линию с тенью
Polygon Рисует многоугольник с тенью
Rectangle Рисует прямоугольник с тенью
Circle Рисует окружность с тенью
FillRectangle Рисует закрашенный прямоугольник с тенью
FillTriangle Рисует закрашенный треугольник с тенью
FillPolygon Рисует закрашенный многоугольник с тенью
FillCircle Рисует закрашенный круг с тенью
FillEllipse Рисует закрашенный эллипс с тенью
Fill Закрашивает область с тенью
TextOut Выводит текст с тенью

 

Демонстрационное видео работы скрипта Blur.mq5, рисующего примитивы с тенями:

Видео 2. Рисование примитивов с тенями

В классе CGauss для расчета цвета тени применяется библиотека численного анализа ALGLIB. В данном классе реализован один вид тени — тень снаружи по диагонали направо вниз со смещением (смотри рис. 4.)

Общая идея класса CGauss: создаем два канваса. Нижний канвас будет выполнять функцию слоя, на котором рисуется тень, а верхний канвас будет выполнять функцию рабочего слоя — на нем будем рисовать графические фигуры. Размеры обоих канвасов равны размеру графика. При этом нижний канвас при создании смещается по горизонтали и вертикали на величину сдвига тени — таким образом облегчается расчет координат рисования теней.

Алгоритм рисования тени работает по следующему принципу: на нижнем канвасе последовательно рисуется количество объектов, равное радиусу размытия. Цвет каждого объекта рассчитывается по алгоритму Гаусса, при этом получается плавный переход от заданного цвета тени к полной прозрачности.

 

Заключение

В статье были рассмотрены алгоритм формирования сглаживания в классе CCanvas, примеры расчета и рисования размытия и теней объектов. При этом в расчетах формирования размытия и теней применяется библиотека численного анализа ALGLIB.

На базе примеров реализации размытия написан класс CGauss, рисующий графические примитивы с тенями.

Прикрепленные файлы |
blur.mq5 (5.93 KB)
pixelsetaa.mq5 (3.99 KB)
shadowtwolayers.mq5 (13.28 KB)
gauss.mqh (24.33 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (10)
Alexander Puzanov
Alexander Puzanov | 8 дек. 2015 в 06:33
Igor Volodin:

Пример: взял прозрачное PNG изображение из онлайн коллекции теней для сайта:

Переводим его в BMP с альфа каналом

OFF: вот отличная тема для 'видеоподсказки от Барабашки' - как прозрачный PNG перегнать в BMP32. Тема актуальна сугубо для MQL-кодеров (слава МетаКвотам), а Владимир как раз специализируется на MQL-графике. Есть несколько вариантов, можно сделать несколько роликов или 1 сводный
Vladimir Karputov
Vladimir Karputov | 8 дек. 2015 в 07:50
Alexander Puzanov:
OFF: вот отличная тема для 'видеоподсказки от Барабашки' - как прозрачный PNG перегнать в BMP32. Тема актуальна сугубо для MQL-кодеров (слава МетаКвотам), а Владимир как раз специализируется на MQL-графике. Есть несколько вариантов, можно сделать несколько роликов или 1 сводный

Есть у меня только один способ конвертации *.png 32 бита с прозрачностью в *.bmp с прозрачностью. Делается с помощью махонькой программы AlphaConv.exe, автор Adam Najmanowicz.

Alexander Puzanov
Alexander Puzanov | 8 дек. 2015 в 08:08
Karputov Vladimir:

Есть у меня только один способ конвертации *.png 32 бита с прозрачностью в *.bmp с прозрачностью. Делается с помощью махонькой программы AlphaConv.exe, автор Adam Najmanowicz.

Я пробовал где-то с год назад её и др варианты - с моими исходными картиками из всех вариантов сработал только Фотошоп
Vladimir Karputov
Vladimir Karputov | 8 дек. 2015 в 08:20
Alexander Puzanov:
Я пробовал где-то с год назад её и др варианты - с моими исходными картиками из всех вариантов сработал только Фотошоп
Я стараюсь использовать по-максимуму бесплатные варианты, поэтому Фотошоп не применяю.
Vasiliy Sokolov
Vasiliy Sokolov | 8 дек. 2015 в 10:14
Andrey Khatimlianskii:

Это личное, не обращайте внимания  )

Мне не очень нравится стиль изложения Владимира, немного занудный

А тут получилось вполне живо.

А какое может быть практическое применение графики в трейдинге?

Многие и про непрактичность трендовых линий говорят, не то что каких-то линейных графиков или теней.

А самое близкое к практике - покупка и продажа, а для них можно сделать кнопки. Так вот они с помощью теней могут быть красивее ;)

Похоже я на своей волне:) Но наверное хорошо, что у нас разные мнения по поводу того, в какую сторону двигать возможности MQL. На мой взгляд красивости вторичны. Более важно, имхо, дать в MQL средства по созданию мощных таблиц, графиков, интеграции с офисным софтом и пакетами статистического анализа. 
Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1) Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1)
С этой статьи я начинаю еще одну серию, относящуюся к разработке графических интерфейсов. На текущий момент нет ни одной библиотеки кода, которая позволяла бы легко и быстро создавать качественные графические интерфейсы в MQL-приложениях. Я имею в виду графические интерфейсы, к которым мы все привыкли в известных операционных системах.
Модуль торговых сигналов по системе Билла Вильямса Модуль торговых сигналов по системе Билла Вильямса
В статье описываются правила торговой системы Билла Вильямса, порядок использования разработанного MQL5-модуля для поиска и разметки на графике паттернов данной системы, автоматической торговли по найденным паттернам, а также представлены результаты тестирования на различных торговых инструментах.
Графические интерфейсы I: Форма для элементов управления (Глава 2) Графические интерфейсы I: Форма для элементов управления (Глава 2)
В этой статье создадим первый и самый главный элемент графических интерфейсов — форму для элементов управления. К этой форме можно будет присоединять множество различных элементов управления в любом расположении и в любых комбинациях.
Индикатор "Канат" Эрика Наймана Индикатор "Канат" Эрика Наймана
В статье описывается построение индикатора «Канат» по книге Эрика Л. Наймана «Малая энциклопедия трейдера». Этот индикатор показывает направление тренда на основе расчетных величин быков и медведей за указанный период. В статье изложены принципы построения и расчета индикатора с примерами кода, на основе индикатора построен эксперт и произведена оптимизация внешних параметров.