English Русский 中文 Español Deutsch 日本語
Estudando a Classe CCanvas. Anti-aliasing e Sombras

Estudando a Classe CCanvas. Anti-aliasing e Sombras

MetaTrader 5Exemplos | 5 outubro 2016, 09:52
1 136 0
Vladimir Karputov
Vladimir Karputov

Índice

 

Introdução

Eu acredito que a exibição de vários efeitos dinâmicos é uma das questões que podem ser resolvidas ao desenhar com a classe CCanvas. Por exemplo, com a implementação de construções gráficas através do algoritmo anti-aliasing obtemos uma aparência mais atraente. Ou é desenhado um novo estilo de exibição da linha do indicador chamado "spline", ou desenhando um indicador dinâmico em uma janela separada, de alguma forma semelhante ao desenho das características de frequência no osciloscópio. Em qualquer caso, o desenho abre novos horizontes de aplicação para desenvolvimentos pessoais.

 

1. Coordenadas e canvas

O Canvas é construído nas coordenadas gráficas. Neste caso, o tamanho do gráfico é medido em pixels. O canto superior esquerdo do gráfico tem as coordenadas (0,0).

Por favor, note que ao desenhar as coordenadas de primitivos, os coloridos são dados exclusivamente no int. E para desenhos primitivos utilizando o método de anti-aliasing PixelSetAA, as coordenadas são dadas em double, as coordenadas no método CircleAA são dadas em int, e o tamanho do círculo — em double.

Método Coordenadas Tamanho
PixelSetAA double -
LineAA int -
PolylineAA int -
PolygonAA int -
TriangleAA int -
CircleAA int double

 

Ou seja, ao dar as coordenadas para o método PixelSetAA, as coordenadas do ponto podem ser semelhante a: (120,3, 25,56). O script PixelSetAA.mq5 desenha duas colunas de onze pontos. Na coluna da esquerda, o incremento para cada ponto ao longo do eixo X é de 0.1 e o incremento ao longo do eixo Y é de 3.0. Na coluna da direita, o incremento para cada ponto ao longo do eixo X é de 0.1 e o incremento ao longo do eixo Y é de 3.1.

A fim de ver como esses pontos são desenhados, os resultados operacionais do script PixelSetAA.mq5 foram ampliados várias vezes:

Fig. 1. A operação do método PixelSetAA

Fig. 1. A operação do método PixelSetAA

Para uma melhor visualização eu adicionei limites para o anti-aliasing e o texto com as coordenadas para o desenho:

Fig. 2. Operação visual do método PixelSetAA

Fig. 2. Operação visual do método PixelSetAA

Como você pode ver, o pixel é colorido com a cor dada apenas nas coordenadas sem fração. No entanto, se o ponto tem uma das coordenadas com fração, então este ponto será desenhado com dois pixels usando uma saturação de cor diferente (coluna da esquerda).

Em ambos os casos, quando coordenadas de um ponto são dadas com fração, então tal ponto é desenhado com três elementos que possuem diversas saturações de cor (coluna da direita). Este desenho em especial com três pixels, mas com várias saturações de cor, permite alcançar o efeito de suavização.

 

2. Algoritmo anti-aliasing

Métodos da classe CCanvas que desenham primitivos com anti-aliasing usam o cálculo comum do método de cor do ponto PixelSetAA para a exibição na tela.

Método O método final do cálculo da imagem
PixelSetAA PixelSetAA
LineAA PixelSetAA
PolylineAA LineAA -> PixelSetAA
PolygonAA LineAA -> PixelSetAA
TriangleAA LineAA -> PixelSetAA
CircleAA PixelSetAA

A demonstração de um método de desenho com anti-aliasing PixelSetAA foi visto na fig. 1.

Acontece que, ao desenhar com o anti-aliasing, o método PixelSetAA atua como base da classe CCanvas. Por isso, acredito que vai ser interessante descobrir como o algoritmo anti-aliasing é implementado.

Deixe-me lembrá-lo que as coordenadas X e Y do método PixelSetAA tem um tipo double, além disso, o método PixelSetAA pode tomar as coordenadas do ponto localizado entre os pixels:

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

Em seguida iremos declarar três arrays. O array rr[] é um array auxiliar para calcular quanto um pixel virtual (que pode ser tirado) cobre os pixels físicos da tela. Os arrays xx[] and yy[] são arrays de coordenadas utilizadas para desenhar pixels, a fim de dar um efeito suavizado à imagem.

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

A figura abaixo demonstra a ligação entre um pixel virtual e uma cobertura de pixels física:

Fig. 3. Cobertura de pixels físicos

Fig. 3. Cobertura de pixels físicos

Isso significa que um pixel virtual (com coordenadas calculadas) tem frequentemente coordenadas com uma fração e pode cobrir quatro pixels físicos simultaneamente. Neste caso, para cumprir o seu dever principal, o algoritmo anti-aliasing requer — colorir estes quatro pixels físicos com a cor de um pixel virtual, mas usando diferentes iterações. Desta forma, ele enganarará a nossa visão — os olhos verão uma imagem ligeiramente desfocada com uma mistura de cores suaves e bordas suaves.

O próximo bloco contém cálculos preliminares. Obtemos os valores arredondados das coordenadas de entrada para o número inteiro mais próximo:

static int    yy[4];
//--- cálculos preliminares
   int    ix=(int)MathRound(x);
   int    iy=(int)MathRound(y);

Para uma melhor compreensão de como uma função matemática MathRound funciona (arredondando para cima ou para baixo, se o número tem uma fração de "0,5"), recomenda-se executar este código:

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)));
  }
//+------------------------------------------------------------------+

e o resultado da execução

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

Seguido pelo cálculo delta dx e delta dy — diferença entre as coordenadas recebidas x e y e os seus valores arredondados ix e iy:

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

Agora temos que verificar: se ambos dx e dy são iguais a zero, então saímos do método PixelSetAA.

double dy=y-iy;
   uchar  a,r,g,b;
   uint   c;
//--- não há necessidade de anti-aliasing
   if(dx==0.0 && dy==0.0)
     {
      PixelSet(ix,iy,clr);
      return;
     }

Se deltas não são iguais a zero, então nós prosseguiremos com a preparação de um conjunto de pixels:

PixelSet(ix,iy,clr);
      return;
     }
//--- preparar array de 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;

Este bloco cria especificamente uma base para a ilusão de uma imagem suavisada.

Para visualizar o funcionamento deste blog, eu escrevi um script PrepareArrayPixels.mq5 e gravei um vídeo explicando como funciona:

Video 1. Operação do script PrepareArrayPixels.mq5

Após o array de pixel ser preenchido, "pesos" são calculados para ver como é que um pixel virtual abrange pixels reais:

yy[2]=yy[2]=iy+1;
//--- calcular o raio e soma de seus quadrados
   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];
     }

E o passo final — desenhando um "borrão":

rrr+=rr[i];
     }
//--- desenhar 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. Sombra de objetos

O desenho das sombras apresenta um contorno mais suave para objetos gráficos, assim é criado um efeito de volume menor, de modo que os objetos gráficos param de parecer laterais. Além disso, as sombras têm uma propriedade muito interessante e benéfica: sombras dos objetos normalmente são transparentes e sobre a superposição de gráficos com sombras, um volume adicional é criado.


3.1. Tipos de sombras

Os tipos mais comuns de sombras são mostrados abaixo:

Fig. 4. Tipos de sombras

Fig. 4. Tipos de sombras

Uma sombra de "auréola" pode ter configuração da largura de uma auréola. Uma sombra "externa diagonal" pode ter uma configuração para um ângulo onde a sombra é deslocada. Ambos os tipos de sombras têm configurações selecionando a cor.

Para selecionar um algoritmo relevante para a elaboração de sombras, temos de analisar do que consiste uma sombra. Aqui é aonde se torna viável ampliar a imagem. Veja a seguir, como sombras da imagem 4 se parecem de modo detalhado:

Fig. 5. Do que uma sombra é feita

Fig. 5. Do que uma sombra é feita

Torna-se claro agora que a sombra "auréola" é construída a partir de várias linhas de 1 pixel de largura. Esses contornos ter uma mudança gradual da saturação de cor.


3.2. Obtendo a distribuição normal

Para obter uma transição suave ao desenhar uma sombra, vamos usar o filtro gráfico mais comum - o Gaussian Blur (informações sobre o algoritmo Gaussian Blur são fornecidas abaixo). Este filtro usa distribuição normal ao calcular transformações aplicadas a cada pixel da imagem. O cálculo do "borrão" de cada pixel da imagem depende do raio de desfocagem (o parâmetro é dado antes de usar o filtro) e deve ser realizado com a devida atenção a todos os pixels adjacentes.

Tirando o fato que um raio de desfocagem seja mencionado, uma grade de pixels de N x N é utilizada para o cálculo:

Fórmula da grade

onde Radius é o raio de desfocagem.

A figura abaixo mostra um exemplo de uma grade de pixel para um raio de desfocagem igual a 3.

Fig. 6. Raio desfocado

Fig. 6. Raio desfocado

Eu não vou cobrir a teoria de cálculo rápido para esse filtro, vou apenas mencionar que uma propriedade separada do filtro Gaussian será utilizada: em primeiro lugar, aplicamos o borrão ao longo do eixo X e, em seguida, avançar para o eixo Y. Ela ajuda a tornar o cálculo mais rápido, sem afetar a qualidade.

A influência dos pixels adjacentes ao pixel calculado é desigual e utiliza uma distribuição normal para cálculo. Quanto mais longe é o pixel de um pixel calculado, menos significativo é o efeito sobre ele. Para calcular distribuição normal através do algoritmo Gaussian, vamos utilizar a análise numérica da biblioteca ALGLIB. Um script GQGenerateRecToExel.mq5 vai nos ajudar demonstrar claramente uma modelagem de distribuição normal. Usando a biblioteca ALGLIB, este script recebe um conjunto de coeficientes de ponderação de distribuição normal e exibe esses valores no arquivo <data catalogue>\MQL5\Files\GQGenerateRecToExel.csv. E assim o gráfico construído sobre a base de dados do arquivo GQGenerateRecToExel.csv se parece:

Fig. 7. Distribuição normal

Fig. 7. Distribuição normal

Ao usar o script GQGenerateRecToExel.mq5 como um exemplo, vamos verificar o exemplo da obtenção do array de coeficientes ponderados de uma distribuição normal. A mesma função GetQuadratureWeights será usada nos scripts a partir deste ponto em diante:

//+------------------------------------------------------------------+
//| Obtem o array de pesos da quadratura                             |
//+------------------------------------------------------------------+
bool GetQuadratureWeights(const double mu0,const int n,double &w[])
  {
   CAlglib alglib;            // membro estático da classe CAlglib
   double      alp[];         // coeficiente alfa do array 
   double      bet[];         // O coeficiente beta do array 
   ArrayResize(alp,n);
   ArrayResize(bet,n);
   ArrayInitialize(alp,1.0);  //inicializa um array alfa numérico
   ArrayInitialize(bet,1.0);  // inicializa um array beta numérico

   double      out_x[];
   int         inf=0;
//| Info    -   códgo de erro:                                       |
//|                 * -3    solução do problema interno não foi      |
//|                         convergido                               |
//|                 * -2    Beta[i]<=0                               |
//|                 * -1    N incorreto foi passado                  |
//|                 *  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);
  }

Esta função preenche o array w[] com coeficientes de ponderação de distribuição normal e também verifica o resultado da chamada da função ALGLIB library através da análise da variável inf.


3.3. Recursos

Ao desenhar uma sombra sobre o canvas, as operações com recursos(ResourceReadImage) estão sendo usadas, como por exemplo, a leitura de dados a partir do recurso gráfico e preenchimento do array com estes dados.

Ao trabalhar com os recursos, deve-se prestar atenção nos arrays de pixel para serem guardados no formato uint (leia mais em: representação de cor ARGB). Você também deve ter conhecimento de como as imagens com largura e altura 2D são convertidas a um array unidimensional. Um algoritmo para a conversão é o seguinte: a colagem subsequente das linhas da imagem de uma fila maior. A figura abaixo mostra duas imagens com tamanho de 4 x 3 pixels e 3 x 4 pixels que são convertidas em um array unidimensional:

Fig. 8. Conversão da imagem para um array unidimensional

Fig. 8. Conversão da imagem para um array unidimensional

 

4. Exemplo do algoritmo Gaussian Blur

Um Gaussian Blur será considerado pela aplicação do algorítmo ShadowTwoLayers.mq5. A inclusão dois arquivos Canvas.mqh e a análise numérica da biblioteca ALGLIB são necessários para a operação do script:

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

Parâmetros de entrada:

//--- entrada
input uint  radius=4;               // raio do "blur"
input color clrShadow=clrBlack;     // cor da sombra
input uchar ShadowTransparence=160; // transparência das sombras
input int   ShadowShift=3;          // troca de sombras
input color clrDraw=clrBlue;        // cor da sombra
input uchar DrawwTransparence=255;  // transparência dos desenhos
//---

Criaremos dois objetos canvas. O canvas inferior irá desempenhar a função de uma camada usada para desenhar uma sombra e o canvas superior irá funcionar como uma camada de trabalho para desenhar figuras gráficas. Os tamanhos de ambos os dois canvas são iguais ao tamanho do gráfico (a descrição da função para a obter a altura e largura do gráfico em pixels não é dado aqui, uma vez que estes exemplos estão disponíveis na seção de documentação: Exemplos de como trabalhar com o gráfico):

//--- criar 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;
     }

Agora vamos desenhar um pouco nos objetos canvas. Primeiro vamos desenhar uma peça de trabalho das figuras das sombras (sombras são desenhadas transparentes por padrão) no canvas inferior, e depois desenhar um retângulo no canvas superior.

//--- desenhar sobre o 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();

Devemos obter a seguinte imagem (atenção: "sombras" retangulares ainda não são desfocadas):

Fig. 9. Sombras ainda não são desfocadas

Fig. 9. Sombras ainda não são desfocadas

Desfocagem será realizada sobre o canvas inferior (CanvasShadow). Para este efeito, você deve ler os dados (ResourceReadImage)a partir do recurso gráfico do canvas inferior (CanvasShadow.ResourceName()) e preencher um array unidimensional(res_data) com esses dados:

//+------------------------------------------------------------------+
//| lê os dados a partir do recurso gráfico                          |
//+------------------------------------------------------------------+
   ResetLastError();
   if(!ResourceReadImage(CanvasShadow.ResourceName(),res_data,res_width,res_height))
     {
      Print("Error reading data from the graphical resource ",GetLastError());
      Print("attempt number two");
      //--- Tentativa número dois: agora a largura e altura da imagem são conhecidas
      ResetLastError();
      if(!ResourceReadImage(CanvasShadow.ResourceName(),res_data,res_width,res_height))
        {
         Print("Error reading data from the graphical resource ",GetLastError());
         return;
        }
     }

O próximo passo envolve a obtenção do conjunto de coeficientes de ponderação de distribuição normal através da função GetQuadratureWeights e decomposição do array unidimensional em quatro arrays: Alpha, Vermelho, Verde e Azul. A decomposição da cor é necessária, principalmente porque os efeitos gráficos devem ser aplicados para cada componente de cor.

//+------------------------------------------------------------------+
//| decomposição de imagens nos componentes 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);
     }

A seção de código a seguir é responsável por um desfoque "mágico". No início, a imagem ficará borrada ao longo do eixo X, seguido pelo mesmo processo ao longo do eixo Y. Esta abordagem segue, separadamente, a partir da propriedade do filtro Gaussiano, no qual permite acelerar cálculos sem comprometer a qualidade. Observe o exemplo do desfoque ao longo dos eixos X da imagem:

//+------------------------------------------------------------------+
//| desfoque horizontal (eixo X)                                     |
//+------------------------------------------------------------------+
   uint XY;             // coordenada de pixel no 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++)                  // ciclo na largura da imagem
     {
      for(uint X=radius;X<res_width-radius;X++)    // ciclo na altura da imagem
        {
         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 os artefatos à esquerda
      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 artefatos à direita
      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];
        }
     }

Assim vemos a relação entre os dois:

for(uint Y=0;Y<res_height;Y++)                  // ciclo na largura da imagem
     {
      for(uint X=radius;X<res_width-radius;X++)    // ciclo na altura da imagem
        {
         ...
        }
     }

Este assentamento garante uma passagem através de cada pixel da imagem:

Fig. 10. Passagem por cada pixel da imagem

Fig. 10. Passagem por cada pixel da imagem

Um entrelaçamento garante o cálculo do desfocamento ao longo do eixo X para cada pixel:

for(uint X=radius;X<res_width-radius;X++)    // ciclo na altura da imagem
        {
         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);
        }

A quantidade de pixels adjuntos igual ao raio de desfocagem é selecionado para cada pixel na esquerda e na direita. Deixe-me lembrá-lo que anteriormente foi utilizado a função GetQuadratureWeights para obter o array dos coeficientes ponderados da distribuição normal. A compatibilidade seguinte é obtida: número de pixels adjacentes à esquerda + pixel para a qual o cálculo da indefinição é realizado + número de pixels adjacentes à direita = número de elementos do array de coeficientes ponderados. Desta forma, cada pixel adjacente corresponde a um valor específico no array de coeficientes ponderados.

Assim a desfocagem é calculada para cada cor: cada pixel adjacente é multiplicado pelo coeficiente ponderado que lhe corresponde e os valores obtidos são resumidos. O seguinte exemplo calcula a desfocagem da imagem em vermelho, onde o raio de desfocagem é igual a 4:

Fig. 11. Cálculo de desfocagem

Fig. 11. Cálculo de desfocagem

Artefatos, que são faixas de pixels que não foram desfocados, permanecem ao longo das bordas da imagem ao aplicar o algoritmo de desfocagem. A largura dessas faixas é igual ao raio de desfocagem. Quanto maior é o raio de desfocagem, maiores são as faixas de pixels que não foram desfocadas. No algoritmo esses artefatos são removidos por copiar os pixels desfocados:

//---remove artefatos à esquerda
      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 artefatos à direita
      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];
        }

Operações de desfocagem similares são realizadas pelo eixo Y. Como resultado, obtemos quatro arrays a1_data[], r1_data[], g1_data[], b1_data[] que possuem os valores desfocados escritos para Alpha, vermelho, verde e azul, respectivamente. Mantém-se a cor coletada a partir destes quatro componentes para cada pixel e aplicá-lo ao canvas 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);

O resultado da indefinição de uma camada com sombras:

Fig. 12. Sombras não são desfocadas

Fig. 12. Sombras não são desfocadas

 

5. Classe para desenhar sombras

O exemplo de desenho sobre o canvas é implementado na classe CGauss. A classe CGauss permite desenhar essas primitivas com sombras:

Inicio Descrição
LineVertical Desenho da linha vertical com uma sombra
LineHorizontal Desenho da linha horizontal com uma sombra
Line Desenho da linha arbitrária com uma sombra
Polyline Desenho da polyline com uma sombra
Polygon Desenho do polígno com uma sombra
Rectangle Desenho do retângulo com uma sombra
Circle Desenho do círculo com uma sombra
FillRectangle Desenho do retângulo preenchido com uma sombra
FillTriangle Desenho do triângulo preenchido com uma sombra
FillPolygon Desenho do polígono preenchido com uma sombra
FillCircle Desenho do círculo preenchido com uma sombra
FillEllipse Desenhar elipse preenchida com uma sombra
Fill Preencher a área com uma sombra
TextOut Apresenta o texto com uma sombra

 

Vídeo de demonstração do script Blur.mq5 que desenha primitivos com sombras:

Vídeo 2. Desenho primitivos com sombras

A análise numérica da biblioteca ALGLIB é usada para calcular a cor da sombra na classe CGauss. Existe um tipo de sombra implementado nesta classe — uma sombra desenhada diagonalmente para fora abaixo à direita, com uma mudança (ver Fig. 4).

A ideia geral da CGauss é criar dois objetos canvas. O canvas inferior irá desempenhar a função de uma camada usada para desenhar uma sombra e o canvas superior irá funcionar como uma camada de trabalho para desenhar figuras gráficas. Tamanhos de ambas os canvas são iguais o tamanho do gráfico. Em que o canvas inferior, ao ser criado, é deslocado horizontalmente e verticalmente pelo tamanho do deslocamento da sombra — desta forma, o cálculo das coordenadas para desenhar as sombras torna-se mais fácil.

O algoritmo do desenho da sombra opera pelo seguinte princípio: a quantidade de objetos igual ao raio de indefinição é posteriormente elaborado no canvas inferior. A cor de cada objeto é calculado através do algoritmo Gaussian, obtendo-se assim uma determinada forma sutil na cor de sombra para completar a transparência.

 

Conclusão

Neste artigo, nós cobrimos o algoritmo anti-aliasing na classe CCanvas, juntamente com os exemplos de cálculos, a desfocagem desenhada e as sombras dos objetos. Com isto, a análise numérica da biblioteca ALGLIB foi aplicada nos cálculos de formação de desfocagem e tons.

Além disso, a classe CGauss para desenhos gráficos primitivos com sombras foi escrito com base em vários exemplos.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/1612

Arquivos anexados |
blur.mq5 (5.93 KB)
pixelsetaa.mq5 (3.99 KB)
shadowtwolayers.mq5 (13.28 KB)
gauss.mqh (24.33 KB)
Trabalhando Com Soquetes em MQL, ou como se tornar um provedor de sinal Trabalhando Com Soquetes em MQL, ou como se tornar um provedor de sinal
Soquetes… O que seria do nosso mundo de TI sem eles? Datado por volta de 1982 e até o presente momento, pouco mudou, eles continuam trabalhando para nós a cada momento. Esta é a base da rede, as terminações nervosas da Matriz que todos nós vivemos.
Expert Advisor multiplataforma: Ordens Expert Advisor multiplataforma: Ordens
MetaTrader 4 e MetaTrader 5 usam regras diferentes para o processamento de pedidos de negociação. Este artigo discute a possibilidade de utilizar o objeto de classe que representa a transação para processamento pelo servidor, graças a isso o Expert Advisor poderá trabalhar com elas independentemente da versão da plataforma de negociação e o modo usado.
A contribuição de Thomas Demark para a análise técnica A contribuição de Thomas Demark para a análise técnica
Este artigo descreve os pontos TD e as linhas TD inventadas por Thomas Demark, mostra sua aplicação na prática, bem como demostra o processo de escrita de três indicadores e dois EAs usando as ideias dele.
Interfaces Gráficas VI: Os Controles Deslizante e Deslizante Duplo (Capítulo 2) Interfaces Gráficas VI: Os Controles Deslizante e Deslizante Duplo (Capítulo 2)
No artigo anterior, nós enriquecemos a nossa biblioteca com quatro controles que são frequentemente usados ​​nas interfaces gráficas: caixa de seleção, campo de edição, campo de edição com caixa de seleção e a lista combinada com a caixa de seleção. O segundo capítulo da sexta parte será dedicado aos controles deslizante e deslizante duplo.