Desenhando formas, gráficos, texto e imagens (2D)

Artigos, manuais, conselhos e dicas sobre programação em AutoHotkey

Moderator: Gio

Post Reply
User avatar
Gio
Posts: 1247
Joined: 30 Sep 2013, 10:54
Location: Brazil

Desenhando formas, gráficos, texto e imagens (2D)

Post by Gio » 14 Aug 2018, 18:12

Olá :angel:

Você sabia que o AutoHotkey pode desenhar diversas formas, como gráficos de barras, círculos, com texto escrito, cores diferentes e etc? E que além disso, essas formas podem ser salvas em um arquivo para impressão ou exibidas em uma tela de um programa seu? E que elas também podem ser inseridas em arquivos de imagem já existente (editando o arquivo)?

As bibliotecas GDI e GDI+ são bibliotecas nativas do windows cujas funções podem ser acessadas através de DllCall(), mas graças a um usuário de nossa comunidade (chamado Tic), o acesso através dos scripts do AutoHotkey se tornou ainda mais fácil, de modo que podemos copiar a biblioteca inteira em código do AutoHotkey e então utilizar as suas funções com a sintaxe de função normal do AutoHotkey (sem precisar de DllCall).

Aprendendo a usar a biblioteca, você poderá criar relatórios customizados para os seus programas, tão profissionais quanto os que você encontra em qualquer programa de escritório. Além disso, poderá utilizar a tecnologia para criar a parte gráfica de jogos 2D e fazer mais um monte de coisas cuja lista só a criatividade pode completar.

Gostou? Então vamos aprender isso desde já! :thumbup:

OBS: Obrigado ao Tic (Tariq Porter) por sua biblioteca GDI+ (https://autohotkey.com/boards/viewtopic.php?f=6&t=6517). A biblioteca encontra-se disponível pelo autor para uso livre, tanto comercial quanto não-comercial.

1. O que é uma biblioteca do AutoHotkey?

O AutoHotkey possui um enorme conjunto de comandos e funções embutidas que possibilitam realizar um monte de coisa em matéria de automação e programação em ambiente Windows. Mas além disso, os usuários do programa também podem criar as suas próprias funções, transformando tarefas complexas que tomariam muitas linhas de código para programar em algo que podemos controlar com apenas uma chamada de função. Nesse sentido, muitos usuários da comunidade já têm contribuído regularmente ao longo dos anos para a expansão das possibilidades e da facilidade de uso da linguagem, disponibilizando verdadeiras bibliotecas de funções (conjuntos de funções que fazem várias coisas relacionadas, como tratamento de imagens no caso da GDIP_All de Tic) para que outros usuários possam copiar e colar em seus scripts, fazendo uso do código já desenvolvido.

Temos fórums específicos para o compartilhamento de funções e bibliotecas de funções, e muitas contribuições importantíssimas já foram agregadas em tópicos nestes fórums. Se você desejar conhecer o que já foi disponibilizado por outros membros da comunidade, ou se desejar incluir suas próprias contribuições, visite o fórum de Scripts e Funções, tanto em português quanto em inglês. Garanto que vai se surpreender com o que já foi criado por nossos membros contribuintes :D

:arrow: Para utilizar uma função ou biblioteca disponibilizada pelo usuário, basta copiar a código e colar embaixo no seu script. Assim, as funções poderão ser chamadas pelo nome. Não se esqueça também de ler o tópico e o código do autor, para ver as possibilidades dos parâmetros e as instruções de uso. Algumas bibliotecas são bem simples, outras precisam que você se dedique um pouco mais para aprender a usar (como a GDI), mas acredite: sempre vale muito a pena :thumbup:


2. Obtendo a biblioteca GDIP_All.ahk

Para obter a biblioteca GDIP_All.ahk, visite o tópico da biblioteca neste link e clique no link de GDIP_All.ahk. Depois, é só copiar o código da página que se abre (todo ele) e colar embaixo no seu script. Depois, você poderá seguir o restante deste tutorial para aprender como usar as funções e criar as imagens.

Conforme dito anteriormente, a biblioteca GDIP_All.ahk foi disponibilizada pelo autor para uso livre comercial e não comercial, e isso significa que você não precisará pagar nada nem pedir autorização para utilizá-la. Assim, como forma de agradecimento e para dar o merecido crédito ao autor, recomendo que você coloque esta mensagem (comentada, para que não interfira no código) no topo do seu script:
Thanks to tic (Tariq Porter) for his GDI+ Library
https://autohotkey.com/boards/viewtopic.php?f=6&t=6517
3. Entendendo a biblioteca GDI+

A biblioteca GDI+ é uma biblioteca com funções especiais para desenho de formas e texto em imagens. Ela foi feita para que esses desenhos sejam criados automaticamente na imagem a partir de comandos intuitivos, operados através de chamadas às suas funções. Isso significa que você não precisa se preocupar em criar loops para recolorir sequências de milhares pixels a partir de cálculos matemáticos de suas posições, mas apenas comandar certas funções que permitem que você indique "o desenhar de um círculo de raio Z na posição X,Y da imagem" (por exemplo). Dessa forma, desenhar se torna bem mais fácil para o programador.

Mas antes de usarmos essa função que desenha um círculo, é importante entender que precisaremos configurar algumas coisas. A GDI foi feita para que possamos nos basear no mundo real na hora de programar nossos desenhos. Isso existe para que o programador se familiarize mais facilmente com ela e também para que possa usá-la com maior facilidade no fim da contas, mas no início do aprendizado do seu uso, essa questão demanda um pouco mais do programador. Vamos explicar isso para que você entenda onde a GDI parece complicar no início, mas facilita bastante no fim. :thumbup:

:arrow: Conforme dito anteriormente, a GDI foi feita para transformar o ambiente de desenho programático em algo próximo do mundo real. Dessa forma, desenhar fica intuitivo e racional.

Assim, usando a ideia de analogia com o mundo real da GDI, temos que pensar as coisas dessa forma: se vamos desenhar um círculo, vamos desenhá-lo em algum lugar. Isso significa que precisaremos então configurar a criação de uma tela (de pintura) virtual para trabalharmos nela. Assim poderemos configurar várias telas (ou uma só) conforme desejarmos, e também poderemos fazer com que elas tenham tamanhos diferentes (por exemplo). Além disso, se vamos desenhar um círculo em uma tela, vamos desenhar este círculo com alguma coisa. Isso significa, por exemplo, que temos que chamar uma função que vai criar um pincel com tamanho 10 e cor azul, por exemplo (se essa for a cor que quisermos, é claro).

Está entendendo onde quero chegar? Criar um círculo envolve configurar a tela e o pincel, e depois comandar o desenho do círculo naquela tela com aquele pincel. Digo isso de modo simbólico a princípio, mas conforme vamos ver a seguir, a analogia é bem próxima.

:arrow: A princípio, ter de configurar estas coisas antes de começar a desenhar pode parecer desnecessário (afinal, poderiamos ter uma função de desenhar e pronto certo?) mas não se esqueça que as bibliotecas utilizam isso para facilitar a coisa para o programador (tornando o ambiente mais familiar com o mundo real) e também para não limitar as possibilidades do programador. Ter de configurar o pincel significa que também que você pode configurar 10 pincéis diferentes e usá-los da forma que quiser em várias telas diferentes se quiser, formando um desenho bem complexo sem muita dificuldade depois que os pincéis já estiverem à mão.

4. Criando o primeiro desenho (Tutorial 1)

Agora que você já entendeu com a GDIP funciona, vamos apresentar um pequeno código-tutorial, baseado naquele que foi escrito pelo prório Tic, que vai demonstrar o funcionamento da GDI para criar uma tela e escrever nela uma elipse (formato oval) e um retângulo, exibindo-os ao usuário em uma janela.

O código está comentado em português para facilitar o entendimento, então verifique nele como configuramos tudo e depois desenhamos as formas, exibindo em uma tela do AutoHotkey.

:!: :arrow: ATENÇÃO !! Para executar o código abaixo, não se esqueça que a biblioteca GDIP_All.ahk deve estar colada embaixo do código, pois é lá que estão os corpos das funções que vamos utilizar.

Code: Select all

; gdi+ ahk tutorial 1 escrito por tic (Tariq Porter) - (traduzido e editado por Gio (Giordanno Sperotto) para os usuários da comunidade do AutoHotkey em português)
; Requer a biblioteca Gdip_All.ahk (cole o código dela embaixo deste código ou utilize o comando #Include para incluí-la a partir de um arquivo ou da pasta padrão das bibliotecas do AutoHotkey)
;
; Tutorial para desenhar uma elipse (formato oval) e um retangulo na tela usando GDI.

#SingleInstance, Force
#NoEnv
SetBatchLines, -1

; PASSO 1: INICIAR UMA SESSÃO DA GDI (A BIBLIOTECA DLL DO WINDOWS QUE VAMOS UTILIZAR)

; Primeiro, iniciamos a gdi+. Isso é feito com uma chamada à função Gdip_Startup(), que retorna um pointer (uma espécie de link de referência) para a sessão da Gdip que estamos abrindo.
If !pGDI := Gdip_Startup()
{
	MsgBox, 48, Erro na gdiplus! , Não foi possível iniciar a Gdiplus do Windows. Por favor, verifique se você possui a biblioteca Gdiplus no seu sistema.
	ExitApp
}
OnExit, Exit

; PASSO 2: CRIAR E CONFIGURAR UMA JANELA DO AUTOHOTKEY (PARA RECEBER O DESENHO).

; Depois, criamos uma janela com opção de múltiplas camadas (opção +E0x80000, que deve ser usada para que a função de atualização da imagem - UpdateLayeredWindow() - funcione!). A janela também será sempre vísivel (AlwaysOnTop), não terá uma barra de tarefas e não terá título visível.
Gui, 1: -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs

; Depois, exibimos a janela criada.
Gui, 1: Show, NA

; Então, coletamos um handle (uma outra espécie de link de referência) para essa janela que criamos, de modo que possamos atualizá-la depois.
hwnd1 := WinExist()

; PASSO 3: CRIAR E CONFIGURAR AS FERRAMENTAS DE DESENHO DA GDI (TELA, PINCÉIS, ADESIVO DE TRANSFERENCIA, ETC).

; Depois, configuramos a largura e a altura da nossa area de desenho.
Largura :=1400, Altura := 1050

; E com essas configurações, criamos um bitmap GDI onde possamos efetivamente fazer o nosso desenho. O bitmap GDI é o que seria uma tela de pintura no mundo real. A função retorna um handle (um tipo de link de referência) ao Bitmap criado, para que possamos passá-lo para outras funções que precisem de uma referência ao bitmap em que deverão operar.
hbm := CreateDIBSection(Largura, Altura)

; Depois nós criamos um meio de conexão (Device Context) entre o bitmap GDI que criamos e a janela onde vamos exibi-lo. Vamos usar esse meio para transferir nosso desenho para a janela depois, então podemos imaginar ele como sendo um adesivo que vamos usar para puxar o desenho da tela de pintura e pregar na janela. A função que cria o meio de conexão (Device Context) retorna um handle (um tipo de link de referência) para o meio de conexão (Device Context).
hdc := CreateCompatibleDC()

; Agora, conectamos o bitmap GDI ao meio de transferência criado, tendo como resultado um objeto-bitmap (obm).
obm := SelectObject(hdc, hbm)

; E depois utilizamos outra função para descobrir o pointer (uma outra espécie de link de referência) para os gráficos do meio de conexão, o qual salvamos em G. Essa função faz isso a partir do handle do meio de conexão. É importante notarmos que um pointer e um handle são duas formas diferentes de referência (como se um fosse o nome do rua e o outro fosse o CEP). Assim, usaremos o handle (hdc) em algumas funções e em outras usaremos o pointer (G), conforme a função que vamos utilizar peça um ou o outro.
G := Gdip_GraphicsFromHDC(hdc)

; Agora, vamos configurar o modo (ou estilo) de desenho que vamos adotar. O 4 significa que toda pincelada que dermos, será com antialias (um efeito suavizante na pincelada).
Gdip_SetSmoothingMode(G, 4)

; Depois, configuramos um pincel de cor vermelha opaca para desenhar o circulo. O código é em base hexadecimal e é constituído de um byte para cada canal de cor nessa ordem: transparência, vermelho, verde e azul). Juntos, os valores dos canais de cor formam a cor e a transparência final do pincel. (funciona mais ou menos assim: unindo vermelho e azul, por exemplo, formamos o roxo, e o valor numérico do vermelho e do azul formam a tonalidade do roxo).
; Experimente depois trocar os valores dos canais e veja como isso muda a cor da elipse que vamos criar com o pincel que configuraremos com essa cor.
; A calculadora do windows tem uma opção de exibir para programadores, de modo que você possa transformar um número entre decimal e hexadecimal.
; Os valores vão de 00 a FF, sendo FF o maior valor possível (255).
Canal_Alfa := "ff" ; Totalmente opaco, ou seja, sem nenhuma transparência.
Canal_Vermelho := "ff" ; Valor máximo no canal vermelho. Deixará nosso pincel bem forte.
Canal_Verde := "00" ; Valor zero no verde. Queremos nosso pincel vermelho puro, por isso o canal verde é zerado totalmente.
Canal_Azul := "00" ; Valor zero no azul. Queremos nosso pincel vermelho puro, por isso o canal azul é zerado totalmente.

COR_DO_PINCEL := "0x" . Canal_Alfa . Canal_Vermelho . Canal_Verde . Canal_Azul ; A cor final é a união hexadecimal dos valores do canais em sequência (primeiro transparencia, depois vermelho, depois verde, depois azul). O resultado é 0xFFFF0000.

; Agora criamos o pincel com a configurações indicadas. Usamos a função Gdip_BrushCreateSolid() e obtemos um pointer (outra espécie de link de referência) para o pincel, de modo que possamos utilizá-lo depois.
p_Pincel_Vermelho := Gdip_BrushCreateSolid(COR_DO_PINCEL)

; Depois, configuramos um outro pincel, de cor azul com uma transparência intermediária. Vamos usar as mesma variáveis, redefinindo os valores, mas poderíamos usar outras também.
Canal_Alfa := "66" ; Transparência 66, ou seja, nosso pincel pintará com uma transparência de 60% (ou em outras palavras, sua opacidade será de 40%).
Canal_Vermelho := "00" ; Valor zero no canal vermelho. Agora nós queremos nosso pincel azul puro, por isso o canal vermelho é zerado totalmente.
Canal_Verde := "00" ; Valor zero no verde. Queremos nosso pincel azul puro, por isso o canal verde é zerado totalmente.
Canal_Azul := "ff" ; Valor máximo no azul. Queremos nosso pincel azul puro, por isso o canal azul é recebe o valor ff (equivalente a 255).

COR_DO_PINCEL := "0x" . Canal_Alfa . Canal_Vermelho . Canal_Verde . Canal_Azul ; A cor final é a união hexadecimal dos valores do canais em sequência (primeiro transparencia, depois vermelho, depois verde, depois azul). O resultado nesse caso é 0x660000FF.

; Criamos o pincel azul com as configurações indicadas. Usamos novamente a função Gdip_BrushCreateSolid() e obtemos um outro pointer (outra espécie de link de referência) para o outro pincel, de modo que possamos utilizá-lo depois.
; Veja que teremos 2 pincéis agora! (este com a referência p_Pincel_Azul e ainda temos o anterior com a referência p_Pincel_Vermelho).
p_Pincel_Azul := Gdip_BrushCreateSolid(COR_DO_PINCEL)


; PASSO 4: DESENHAR NA TELA

; Agora finalmente começaremos a desenhar. 
; Primeiro, desenhamos a elipse usando o pincel vermelho. Faremos isso usando a função Gdip_FillEllipse(), que tomará como parâmetros o pointer do meio de conexão (G), o pointer do pincel desejado (usaremos o pincel vermelho, indicado através de seu pointer) as coordenadas (X,Y) iniciais dentro da área de desenho e finalmente a largura e tamanho da elipse desejada (W,H).
Gdip_FillEllipse(G, p_Pincel_Vermelho, 100, 500, 200, 300)


; Depois, desenhamos o retângulo usando o pincel azul. Faremos isso usando a função Gdip_FillRectangle(), que tomará como parâmetros o pointer do meio de conexão (G), o pointer do pincel desejado (usaremos o pincel azul agora, indicado através de seu pointer) as coordenadas (X,Y) iniciais dentro da área de desenho e finalmente a largura e o tamanho do retângulo desejado (W,H).
Gdip_FillRectangle(G, p_Pincel_Azul, 250, 80, 300, 200)

; PASSO 5: TRANSFERIR O DESENHO PARA A JANELA.


; E finalmente atualizamos a nossa janela, transferindo o nosso desenho para ela. Para isso, usaremos a função UpdateLayeredWindow(). Ela toma os seguintes parâmetros: o handle para a janela alvo (pegamos isso lá encima usando o WinExist()), o handle para o meio de conexão (não confundir com o pointer, pois são tipos diferentes de referência), as posições X e Y iniciais do desenho na tela, e finalmente a Largura e a Altura do desenho na tela. 
UpdateLayeredWindow(hwnd1, hdc, 0, 0, Largura, Altura)


; PASSO 6: DELETAR O QUE NÃO PRECISAMOS MAIS (LIMPAR A MEMÓRIA)

; Agora que já fizemos tudo o que precisamos para exibir a tela com a imagem, só nos falta limpar a bagunça. Isso é importante pois os pincéis, telas, sessão da Gdi+ e etc tomam espaço da memória do computador. Em um primeiro momento, você pode pensar que limpar a memória é algo chato, mas faça disso um costume, pois estamos trabalhando com edição de imagens e um programa maior poderá tornar o computador lento se você não o fizer. O código a seguir explica como deletamos cada item que criamos até aqui.

; Primeiro deletamos o nosso pincel vermelho.
Gdip_DeleteBrush(p_Pincel_Vermelho)

; Depois deletamos o nosso pincel azul.
Gdip_DeleteBrush(p_Pincel_Azul)


; Depois, selecionamos o objeto de volta ao hdc
SelectObject(hdc, obm)

; E então podemos deletar o bitmap.
DeleteObject(hbm)

; E não podemos esquecer que o meio de conexão também deve ser deletado.
DeleteDC(hdc)

; E o pointer para o meio de conexão também deve.
Gdip_DeleteGraphics(G)
Return

;#######################################################################

Exit:
; E finalmente, atrelamos o fechar do programa à finalização da sessão da Gdi que criamos. Não podemos deletar ela antes disso, mas devemos fazê-lo quando isso ocorrer.
Gdip_Shutdown(pGDI)
ExitApp
Return
Observando o código acima, perceba que dividimos ele em 6 passos. Como o tutorial 1 trata basicamente de desenhar uma elipse e um retangulo em uma janela e exibir essa janela, temos a seguinte disposição dos passos:
  • 1. Iniciar uma sessão do GDI.
  • 2. Criar e configurar a janela do Autohotkey (que vai receber o desenho).
  • 3. Criar e configurar as ferramentas de desenho da GDI (Tela de pintura, pincéis, adesivo de transferência, etc).
  • 4. Desenhar na tela de pintura
  • 5. Transferir o desenho da tela de pintura para a janela.
  • 6. Deletar os elementos que não vamos usar mais
Todos esses passos são individualmente importantes, mas entender a sequência deles também é. Não é possível desenhar na tela sem ter os pincéis e a própria tela. Também não será possível transferir o desenho para a janela sem ter a janela. Tudo é interligado. Mas é também lógico e intuitivo, desde que dediquemos algum tempo para pensar sobre o conjunto.

Assim, se quisermos adicionar uma outra elipse no código acima, como faríamos? Ora, a janela a ser criada é uma só, e o código ja existe. Podemos usar um pincel já existente ou então criar outro. E devemos de qualquer forma fazer o desenho na tela antes de transferir da tela para a janela. Logo, a posição das novas chamadas e o que temos que adicionar pode ser deduzida dessa forma.


5. Outros exemplos (Tutoriais 2 a 10 do tic)

A biblioteca GDI pode ser usada para muitas outras coisas além de exibir os desenhos criados em uma tela. Podemos salvá-los em arquivos, adicionar fontes, criar formas com apenas o contorno, bordas arredondadas, etc, etc. Com um pouco de criatividade, podemos criar desde gráficos em barras ou pizza até relatórios profissionais completos com linhas divisórias, texto padrão, com fontes específicas e muito mais.

Assim, vou colocar nesta seção a lista dos tutoriais originais que o Tic disponibilizou em seu tópico original. Se você quiser ver algum deles, basta executar e analisar o código a partir das premissas que descrevemos até aqui. Conforme puder, também vou traduzir os comentários destes tutoriais e colocá-los em forma de código neste próprio tópico. Esperamos que use esses exemplos apenas para se inspirar, pois as possibilidades da GDI são muito maiores :thumbup:

Exemplo 2: Gdip.Tutorial.2-Draw.Outlined.Shapes.ahk - Criar uma janela simples e desenhar nelas os contornos de uma elipse e um retângulo (somente os contornos)

Exemplo 3: Gdip.Tutorial.3-Create.Gui.From.Image.ahk - Criar uma janela a partir de um arquivo de imagem no seu computador

Exemplo 4: Gdip.Tutorial.4-Draw.Circles.ahk - Criar uma janela maximizada (fullscreen) e encher ela de elipses geradas randomicamente (um efeito visual bacana).

Exemplo 5: Gdip.Tutorial.5-Create.Bitmap.ahk - Colocar algumas formas em um bitmap e savar em um arquivo ".png".

Exemplo 6: Gdip.Tutorial.6-Image.Editing.ahk - Pegar arquivos de imagem do computador, carregar eles em um plano de fundo e depois salvar em um arquivo ".png".

Exemplo 7: Gdip.Tutorial.7-Draw.draggable.rounded.rectangle.ahk - Desenhar um retângulo com as bordas arredondadas e permitir que o usuário o arraste pela tela apenas clicando com o mouse e segurando.

Exemplo 8: Gdip.Tutorial.8-Write.text.onto.a.gui.ahk - Pegar o retangulo com bordas arredondadas e adicionar texto nele, mostrando várias opções de texto possíveis (fonte, tamanho, cor, etc).

Exemplo 9: Gdip.Tutorial.9-Create.a.progress.bar.on.standard.gui.ahk - Criar uma barra de progresso com um aspecto visual bacana usando a biblioteca em uma janela padrão do AutoHotkey.

Exemplo 10: Gdip.Tutorial.10-Rotate.Flip.or.Mirror.an.image.ahk - Rodar uma imagem, virar de lado ou espelhar ela em uma janela de camadas.

6. Considerações finais

Este foi mais um tutorial com o objetivo de demonstrar o mundo de possibilidades que o AutoHotkey traz. Sempre que nos deparamos com novas implementações, crescemos a nossa percepção sobre como as coisas que já vimos os programas fazerem em um computador podem ser feitas. Espero trazer mais alguns tutoriais para o nosso fórum em português, de modo a auxiliar nossos membros a crescerem seu interesse pelo AutoHotkey.

Se tiver alguma dúdiva sobre a GDI ou sobre como montar um relatório ou imagem, sinta-se livre para pergunta neste tópico ou no fórum Eu Preciso de Ajuda. Um forte abraço e até mais :wave:
Post Reply

Return to “Tutoriais”