Estudando a Classe CCanvas. Anti-aliasing e Sombras
Índice
- Introdução
- 1. Coordenadas e canvas
- 2. Algoritmo anti-aliasing
- 3. Sombra de objetos
- 4. Exemplo do algoritmo Gaussian Blur
- 5. Classe para desenhar sombras
- Conclusão
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
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
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
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.
Os tipos mais comuns de sombras são mostrados abaixo:
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
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:
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
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
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.
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
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
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
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
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
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
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso