Informática Numaboa - Tutoriais e Programação

Janela Numaboíssima (masm)

Qui

16

Abr

2009


00:00

  • Imprimir
(2 votos, média 5.00 de 5) 


Nível avançado

Neste tutorial vamos sair do feijão com arroz da janela retangular do Windows e criar um visual totalmente novo. Para criar um gráfico que servirá de pele (skin) para a nossa janela, vamos finalmente programar "de verdade" em assembly.

Os mnemônicos utilizados neste tutorial são os de uso mais frequente - é bom a gente começar a se acostumar com eles. Caso você não saiba quais são, dê uma chegadinha na Oficina de Informática e leia o tutorial Curso Relâmpago de Assembly.

Mais sobre o contexto de dispositivo

Uma das principais características da API do Windows é a sua independência de dispositivos - os aplicativos win32 podem desenhar e imprimir numa grande variedade deles. O software que dá apoio a essa independência são duas DLLs: a GDI.DLL (Graphics Device Interface - interface de dispositivos gráficos) e um driver de dispositivo (device driver). O driver é próprio do dispositivo usado pelo aplicativo. Por exemplo, se o aplicativo for desenhar na área cliente da sua janela num monitor VGA, a biblioteca usada será a VGA.DLL; se for desenhar numa impressora, será SuaImpressora.DLL.

O aplicativo precisa indicar para a GDI qual driver deve ser carregado e, depois de carregado, precisa preparar o dispositivo para a operação de desenho: escolher a espessura e a cor das linhas, o padrão e a cor do pincel, a fonte, a região de corte, etc. Estas tarefas são efetuadas através de um contexto de dispositivo.

Um contexto de dispositivo é uma estrutura que define um conjunto de objetos gráficos e seus respectivos atributos, além dos modos gráficos que afetam a saída. Para as operações de desenho e pintura, os objetos gráficos incluem uma caneta para desenhar linhas, um pincel para pintar e preencher, um bitmap para copiar ou fazer a rolagem de porções da tela, uma paleta para definir o conjunto de cores disponíveis, uma região para cortes e outras operações e um caminho. Diferentemente de outras estruturas win32, um aplicativo nunca pode acessar diretamente um contexto de dispositivo - sempre atua indiretamente sobre a estrutura através de chamadas a diversas funções.

Os contextos de dispositivo de memória

Neste tutorial o interesse maior é nos contextos de dispositivo de memória. Estes contextos armazenam imagens do tipo bitmap para um dispositivo em particular. O formato de cor para o bitmap criado é compatível com o dispositivo associado, por isto este tipo de contexto de dispositivo também é conhecido como contexto de contexto compatível.

O bitmap original num contexto de dispositivo de memória é apenas marcador e sua dimensão é de 1 x 1 pixel. Para que um aplicativo possa começar a desenhar, primeiro é preciso selecionar um objeto bitmap para o contexto, indicando as dimensões (largura e altura) apropriadas. Só depois disto é que o aplicativo pode começar a usar o contexto para armazenar imagens. É coisa do tipo "abrir o buraco" para depois "plantar" o gráfico. Não confunda objeto bitmap (o buraco) com a imagem gráfica (a planta).

Quando um aplicativo cria um contexto de dispositivo, o Windows lhe atribui automaticamente um conjunto de objetos padrão (caneta, pincel, paleta, região). Só não existe como padrão um bitmap e um caminho (para formas geométricas). Um aplicativo também pode criar um novo objeto e incluí-lo no contexto de dispositivo.

Os tipos de contexto de dispositivo existentes são: display (pintura em vídeo), printer (pintura em impressora), memory (operações de pintura em bitmaps) e information (dispositivos de dados).

Um pouco sobre bitmaps

A tradução literal de Bitmap é mapa de bits, ou seja, a cor de cada pixel é identificada por um conjunto de bits. O conjunto de cores designadas para um determinado gráfico é chamado de paleta de cores. Um bitmap de 16 cores possui uma paleta com 16 cores possíveis, portanto, precisamos de 4 bits para poder numerar cada uma das cores. Veja o exemplo abaixo:

Decimal Hexa Binário Cor
0 0 0000 preto
1 1 0001 vermelho escuro
2 2 0010 verde escuro
3 3 0011 amarelo escuro
4 4 0100 azul escuro
5 5 0101 magenta escuro
6 6 0110 turquesa escuro
7 7 0111 cinza escuro
8 8 1000 cinza claro
9 9 1001 vermelho claro
10 A 1010 verde claro
11 B 1011 amarelo claro
12 C 1100 azul claro
13 D 1101 magenta claro
14 E 1110 turquesa claro
15 F 1111 branco

Com a paleta definida, podemos montar um bitmap sob a ótica de um programador: basta mapear a sequência de pixels atribuindo-lhes os valores das suas cores. No exemplo abaixo, o gráfico é uma área retangular de 9 x 9 pixels e mostra uma casinha. Note que as linhas são mapeadas de baixo para cima porque se trata de um DIB (device independent bitmap - bitmap independente de dispositivo), um tipo muito comum de bitmap. Na coluna "Mapeado em Hexa" encontram-se os valores da cor de cada um dos pixels de acordo com o padrão da paleta de cores. Poderíamos ter adicionado uma coluna com o mapeamento em binário o que, na verdade, seria a representação "real" do bitmap e é de onde deriva o nome deste tipo de imagem.

 
Linhas Mapeado em Hexa
                 
                 
                 
                 
                 
                 
                 
                 
                 
linha 8 0 0 0 0 9 0 0 0 0
linha 7 0 0 0 9 9 9 0 0 0
linha 6 0 0 9 9 9 9 9 0 0
linha 5 0 9 9 9 9 9 9 9 0
linha 4 9 F F F F F F F 9
linha 3 0 F F F F F F F 0
linha 2 0 F F 1 F 1 F F 0
linha 1 0 F F 1 F 1 F F 0
linha 0 0 F F 1 F 1 F F 0

As janelas como bitmap

As janelas nada mais são do que áreas retangulares numa região da tela. Cada uma possui seu próprio modelo de contexto de dispositivo, que contém os objetos gráficos usados para desenhá-la e pintá-la. Portanto, cada janela possui um bitmap que lhe confere a aparência. Se a área retangular estiver totalmente preenchida por pixels coloridos, é claro que a janela terá o formato retangular. Se a área retangular tiver pixels transparentes, apenas as áreas de pixels visíveis é que irão compor o formato da janela. Sabendo disto, podemos desenhar janelas com "buracos", com contornos arredondados ou outras características visuais partindo de uma área retangular e tornando porções desta área "invisíveis" ou transparentes. Precisamos apenas fornecer o bitmap adequado ao dispositivo de contexto modelo janela.

Use o programa gráfico da sua preferência para criar o gráfico que servirá de máscara para a janela. Use a mesma cor em todas as áreas do gráfico que você quer que "desapareçam", pois esta cor é que será transformada em "transparente". É claro que precisa ser uma cor diferente de todas as que você usar para as áreas não transparentes. ANOTE as dimensões do gráfico em pixels e salve-o em formato bitmap (.bmp). O gráfico do exemplo tem 350 x 200 pixels e a cor magenta foi escolhida para ser a transparente:

Bitmap numaBoa.bmp Aparência da janela
Numaboa NumaboaT

Este bitmap, para ser incorporado ao executável, precisa ser adicionado ao arquivo de recursos. No tutorial "Usando Recursos" você encontra uma descrição detalhada de como proceder (não se esqueça de compilar o arquivo de recursos). A referência a este gráfico no arquivo RSRC.RC será a seguinte:

1000 BITMAP "numaBoa.bmp"

Criando nossa classe janela

Nossa janela deverá ter as mesmas dimensões que o bitmap que servirá de máscara. Podemos inicializar duas constantes com estes valores ou usar os valores quando formos criar a classe. Vamos usar a primeira opção. O fundo da janela pode (e deve) ser NULL. IMPORTANTE é que a janela seja do tipo popup.

.386 ... .DATA NomeClasse db "Numaboissima", 0 TituloJanela db "Janela NumaBoíssima", 0 .CONST BitmapID equ 1000 largBitmap equ 350 altBitmap equ 200 .CODE inicio: ... gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD ... mov ej.hbrBackground, NULL ... invoke CreateWindowEx, NULL, ADDR NomeClasse, ADDR TituloJanela, WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, largBitmap, altBitmap, NULL, NULL, mInst, NULL ... gerenteJanela endp ...

Interceptando o WM_CREATE

No momento em que esta janela for criada, queremos que ela faça uso do nosso bitmap e não do padrão do sistema. Faremos o trabalho de pintura para que ele fique de acordo com o nosso projeto.

Carregando o bitmap

A primeira providência é carregar o bitmap desejado. Para isto vamos usar a função LoadBitmap (também descrita em "Usando Recursos") e guardar o manipulador do bitmap na variável global mBitmap.

.386 ... .DATA? mInstancia DWORD ? ... mBitmap DWORD ? .DATA NomeClasse db "Numaboissima", 0 TituloJanela db "Janela NumaBoíssima", 0 .CONST BitmapID equ 1000 largBitmap equ 350 altBitmap equ 200 .CODE inicio: ... gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD ... invoke CreateWindowEx, NULL, ADDR NomeClasse, ADDR TituloJanela, WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, largBitmap, altBitmap, NULL, NULL, mInst, NULL ... gerenteJanela endp gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg == WM_CREATE invoke LoadBitmap, mInstancia, BitmapID mov mBitmap, eax ... gerenteMensagem endp ...

Criando um contexto de dispositivo de memória

Para podermos manipular nosso gráfico antes de apresentá-lo na tela é preciso criar um contexto de dispositivo do tipo memória. É como se fosse o rascunho de trabalho. A função que cria automaticamente um contexto de dispositivo de memória é a CreateCompatibleDC, que pede como parâmetro o manipulador do dispositivo de saída. Se este parâmetro for NULL, a função cria um contexto de dispositivo de memória compatível com a tela atual do aplicativo. O manipulador obtido será guardado na variável local mCMMem.

HDC CreateCompatibleDC( HDC hdc // manipulador para o contexto modelo de memória ); gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL mCMMem:HDC .IF uMsg == WM_CREATE invoke LoadBitmap, mInstancia, BitmapID mov mBitmap, eax invoke CreateCompatibleDC, NULL mov mCMMem, eax ... gerenteMensagem endp ...

Inserindo o bitmap no contexto modelo de memória

Agora é necessário inserir o objeto bitmap que contém o gráfico da nossa janela no contexto de dispositivo de memória para que possamos utilizá-lo como modelo de pintura. A função SelectObject faz o trabalho:

HGDIOBJ SelectObject( HDC hdc, // manipulador do contexto modelo HGDIOBJ hgdiobj // manipulador do objeto ); gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL mCMMem:HDC .IF uMsg == WM_CREATE invoke LoadBitmap, mInstancia, BitmapID

mov mBitmap, eax invoke CreateCompatibleDC, NULL mov mCMMem, eax invoke SelectObject, mCMMem, mBitmap invoke ReleaseDC, hWnd, mCM

... gerenteMensagem endp ...

Obtendo as coordenadas da área da janela

As coordenadas de tela do canto superior esquerdo e inferior direito da janela servem de referência para definir a área retangular total da janela. Chamando a função GetWindowRect obtemos os quatro valores que serão armazenados numa estrutura do tipo RECT.

BOOL GetWindowRect( HWND hWnd, // manipulador da janela LPRECT lpRect // endereço da estrutura para as coordenadas da janela );

De posse destas coordenadas, vamos chamar a função grafiti. Esta é uma função própria (não é da API do Windows), cheia de emoções wink

gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL mCMMem:HDC LOCAL retang:RECT .IF uMsg == WM_CREATE invoke LoadBitmap, mInstancia, BitmapID mov mBitmap, eax invoke CreateCompatibleDC, NULL mov mCMMem, eax invoke SelectObject, mCMMem, mBitmap invoke GetWindowRect, hWnd, ADDR retang invoke grafiti, mCMMem, retang.right, retang.bottom ... gerenteMensagem endp ...

Criando a Região Gráfica

Agora vamos entrar no "filé" do projeto. Todas as condições necessárias para podermos pintar nossa janela estão preparadas. Agora pegue a lata de tinta e dá-lhe pintura! Para efetuar este serviço, vamos criar uma função própria, a grafiti.

Declarando uma função própria

A função grafiti precisa de três parâmetros para trabalhar, todos do tipo DWORD: o manipulador do contexto de dispositivo de memória, a altura e a largura da janela. Vamos escrever seu protótipo, seu cabeçalho e seu final:

.386 .. gerenteJanela proto :DWORD, :DWORD, :DWORD, :DWORD grafiti proto :DWORD, :DWORD, :DWORD

... grafiti proc USES ESI EDI EBX _mModelo:HDC, _largura:DWORD, _altura:DWORD grafiti endp
A palavra-chave USES

Esta declaração do procedimento contém uma novidade: foi incluída a palavra-chave USES.

Quando você programa para Win32, é preciso conhecer algumas regras importantes. Uma dessas regras é que o Windows usa ESI, EDI, EBP e EBX internamente e não espera que os valores destes registradores sejam alterados. Portanto, lembre-se da primeira regra: se você usar qualquer um destes quatro registradores numa função callback, não esqueça de restaurar seus valores originais antes de devolver o controle ao Windows. Uma função callback é uma função sua que é chamada pelo Windows. Isto não significa que você não possa utilizar estes quatro registradores. Apenas certifique-se de que seus valores sejam restaurados antes de devolver o controle ao Windows.

O MASM possui uma palavra-chave opcional que pode ser usada com PROC. Esta palavra-chave faz com que o assembler gere código para "pushar" para a pilha o valor dos registradores que devem ser preservados (e que serão alterados no procedimento) e para reavê-los da pilha com pop quando o procedimento retorna. USES aceita uma lista de registradores separados por um espaço.

Identificando a cor transparente

Partimos do princípio de que o primeiro pixel (o das coordenadas 0,0) tenha a cor que queremos transparente. O que precisamos é identificar todos os pixels que tenham uma cor diferente da cor que deve ficar transparente e fazer uma cópia dos mesmos. Ficando no exemplo acima, seria como se usássemos nosso bitmap original e fizéssemos apenas a cópia dos pixels diferentes de preto:

Pixels Originais Cópia dos Pixels
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 

Para obter a cópia desejada, vamos analisar todos os pixels do bitmap percorrendo todas as linhas, coluna a coluna. O registrador EDI mostrará o número da coluna e o ESI o número da linha. Para usá-los no nosso procedimento, a primeira providência será zerá-los com XOR. O valor da cor transparente, em RGB (red, green, blue), ficará na variável local corT.

A função GetPixel, da gdi.lib, obtém o valor RGB (red, green, blue) do pixel especificado pelas coordenadas.

COLORREF GetPixel( HDC hdc, // manipulador do contexto modelo int XPos, // coordenada x do pixel int nYPos // coordenada y do pixel ); .386 ... include \masm32\include\user32.inc include \masm32\include\gdi32.inc ... includelib \masm32\lib\user32.lib includelib \masm32\lib\gdi32.lib ... grafiti proc USES ESI EDI EBX _mModelo:HDC, _largura:DWORD, _altura:DWORD LOCAL corT:DWORD xor edi, edi xor esi, esi invoke GetPixel, _mModelo, 0, 0 mov corT, eax grafiti endp
Verificando cada pixel

Com a ajuda de rótulos, vamos fazer primeiro um loop para percorrer todos os pixels do bitmap e comparar a cor do pixel atual com a cor transparente.

grafiti proc USES ESI EDI EBX _mModelo:HDC, _largura:DWORD, _altura:DWORD LOCAL corT: DWORD xor edi, edi ; zera o registrador edi xor esi, esi ; zera o registrador esi invoke GetPixel, _mModelo, 0, 0 ; obtém a cor do primeiro pixel mov corT, eax ; inicializa corT com a cor da transparência _olhaPix: invoke GetPixel, _mModelo, edi, esi ; obtém a cor do pixel atual cmp eax, corT ; compara a cor atual com a transparente _proxPix: inc edi ; incrementa a coluna cmp edi, _largura ; compara a coluna com a largura do bitmap jbe _olhaPix ; se for menor ou igual, olha o próximo pixel ; se for maior... xor edi, edi ; zera o contador de colunas (coluna 0) inc esi ; passa para a próxima linha cmp esi, _altura ; compara a linha com a altura do bitmap jb _olhaPix ; se for menor, olha o próximo pixel _retorna: ; termina o processo grafiti endp
Copiando os pixels não transparentes

No nosso exemplo, na segunda fileira, quando alcançarmos a coluna 4, encontraremos o primeiro pixel vermelho. Na coluna 6 encontra-se o último pixel vermelho desta série. Portanto, teremos que copiar os pixels 4, 5 e 6 desta fileira. Para determinar as coordenadas desta região numere as linhas horizontais e verticais a partir do ponto 0,0. A segunda linha horizontal é a linha 1 (x = 1) e a quarta linha vertical é a linha 3 (y = 3). Estas são as coordenadas do canto superior esquerdo da região (1,3). Repetindo o raciocínio para o último pixel vermelho desta fileira, verificamos que seu canto inferior direito é delimitado pela terceira linha horizontal (linha 2) e pela sétima linha vertical (linha 6). Suas coordenadas são x = 2 e y = 3).

No esquema abaixo estão as coordenadas de cada região que deverá ser copiada:

0,0  
Coordenada Superior Esquerda Coordenada Inferior Direita
 
                 
                 
                 
                 
                 
                 
                 
                 
                 
x = 4 e y = 0 x = 5 e y = 1
x = 3 e y = 1 x = 6 e y = 2
x = 2 e y = 2 x = 7 e y = 3
x = 1 e y = 3 x = 8 e y = 4
x = 0 e y = 4 x = 9 e y = 5
x = 1 e y = 5 x = 8 e y = 6
... ...
   
   

A lógica do nosso procedimento será a seguinte:

  • Percorrer cada fileira analisando bit por bit.
  • Quando encontrar o primeiro bit não transparente, marcar as coordenadas do canto superior esquerdo.
  • Continuar até encontrar o próximo bit transparente (acabou a sequência dos coloridos) ou o fim da fileira:
    • marcar as coordenadas do canto inferior direito
    • fazer a cópia dos bits coloridos usando as coordenadas obtidas
    • se for a primeira cópia, esta inicializará o "copião"
    • se não for a primeira cópia, adicioná-la ao "copião"
  • Retornar o copião

Os pontos vitais desta rotina são: identificar uma sequência de pixels não transparentes e determinar a primeira cópia (que servirá de "copião"). Para isto faremos uso de duas variáveis locais. A variável temCor nos indicará se o pixel analisado é transparente ou não e a variável fazerCopiao indicará se o "copião" já foi inicializado.

Convencionaremos que o primeiro pixel seja da cor que queremos transparente - é o pixel indicador de transparência. Sabendo disto, no início do processo a variável temCor deve ser falsa (FALSE) e depois sempre deve refletir o estado do pixel que está sendo analisado.

A primeira cópia de pixels não transparentes servirá para inicializar o "copião"; as cópias subsequentes serão apenas adicionadas ao mesmo. Portanto, no início do processo, precisamos indicar que o "copião" precisa ser feito, ou seja, fazCopiao deve ser verdadeiro (TRUE).

No esquema abaixo, siga os pontos (•) e acompanhe a lógica:

Fileira 1
(linha 0)
fazCopiao temCor esi (linha) Faz cópia de
ebx,esi edi,esi+1
edi ebx
                TRUE FALSE 0   3 -> 4  
                TRUE FALSE -> TRUE 0   4 -> 5 4
                TRUE -> FALSE TRUE -> FALSE 0 4,0 e 5,1 5  
                FALSE FALSE 0   5 -> 6  

Fileira 2
(linha 1)
fazCopiao temCor esi (linha) Faz cópia de
ebx,esi edi,esi+1
edi ebx
                FALSE FALSE 1   2 -> 3  
              FALSE FALSE -> TRUE 1   3 -> 4 3
                FALSE TRUE 1   4 -> 5  
                FALSE TRUE 1   5 -> 6  
                FALSE TRUE -> FALSE 1 3,1 e 6,2 6  
                  (adiciona ao copião)          

Para abrigar as cópias das regiões com pixels não transparentes vamos precisar de um contexto de dispositivo que funcionará como um temporário, o CMtemp, e de um contexto de dispositivo para o "copião", o CMcopiao. Agora é possível completar nosso processo:

grafiti proc USES ESI EDI EBX _mModelo:HDC, _largura:DWORD, _altura:DWORD LOCAL corT:DWORD LOCAL copiao:DWORD LOCAL temCor:DWORD LOCAL CMcopiao: DWORD LOCAL CMtemp: DWORD xor edi, edi ; zera o registrador edi (coluna 0) xor esi, esi ; zera o registrador esi (linha 0) mov temCor, FALSE ; como o primeiro pixel é transparente, ; temCor precisa ser falso mov copiao, TRUE ; o copião precisa ser feito invoke GetPixel, _mModelo, 0, 0 ; obtém a cor do primeiro pixel mov corT, eax ; inicializa corT com a cor da transparência _olhaPix: invoke GetPixel, _mModelo, edi, esi ; obtém a cor do pixel da linha atual na coluna atual cmp eax, corT ; a cor do pixel atual é transparente? jz _copiaPix ; sim, então verifica em _copiaPix se é o primeiro transparente depois de colorido(s) cmp edi, _largura ; não é transparente. Está dentro da largura? jnz _acheiPix ; sim, então atualiza o indicador temCor _copiaPix: cmp temCor, TRUE ; o pixel anterior era colorido? jnz _proxPix ; não, então vai para o próximo pixel mov temCor, FALSE ; sim, mas o atual é transparente mov eax, esi ; pega a coordenada x superior inc eax ; incrementa para obter a coordenada x inferior invoke CreateRectRgn, ebx, esi, edi, eax ; faz a cópia da região de pixels coloridos mov CMtemp, eax ; põe a cópia no contexto de dispositivo temporário cmp copiao, TRUE ; a cópia temporária é a primeira? jnz _poeCopiao ; não, então adicione ao copião push CMtemp ; sim, pega a cópia temporária pop CMcopiao ; e a transforma em copião mov copiao, FALSE ; desliga o indicador jmp _proxPix ; e analisa o próximo pixel _poeCopiao: invoke CombineRgn, CMcopiao, CMcopiao, CMtemp, RGN_OR ; combina a cópia temporária com o copião invoke DeleteObject, CMtemp ; "limpa" o contexto modelo temporário jmp _proxPix ; e analisa o próximo pixel _acheiPix: cmp temCor, FALSE ; pixel é colorido. O indicador já foi ajustado? jnz _proxPix ; sim, então analise o próximo pixel mov temCor, TRUE ; não, então ajuste o indicador temCor mov ebx, edi ; e guarde a coordenada x em ebx _proxPix: inc edi ; incrementa a coluna cmp edi, _largura ; a coluna é a última da linha? jbe _olhaPix ; se for menor ou igual, olha o próximo pixel ; se for maior... xor edi, edi ; é maior, então zera o contador de colunas inc esi ; passa para a próxima linha cmp esi, _altura ; a linha é a última do bitmap? jb _olhaPix ; não, então olha o próximo pixel _retorna: mov eax, CMcopiao ; sim, então põe o copião em eax para retornar ret ; retorna para o ponto de chamada grafiti endp
Regiões

Foram usadas algumas funções que ainda não foram vistas. A CreateRectRgn (que cria uma região retagular), a CombineRgn (que funde duas regiões retangulares) e a DeleteObject, todas da GDI32.DLL.

Uma região é um retângulo, polígono ou elipse (ou a combinação de duas ou mais destas formas) que pode ser preenchida, pintada, invertida, emoldurada e que pode ser usada para testar a localização do cursor (chamado de hit testing). CreateRectRgn cria uma região retangular, CreateEllipticRgn cria uma região elíptica, e assim por diante. As regiões assim criadas podem ser inseridas num contexto de dispositivo para que o aplicativo possa operar sobre elas.

A função CreateRectRgn cria uma região retangular e retorna um manipulador para a região criada:

HRGN CreateRectRgn( int nLeftRect, // coordenada x do canto superior esquerdo da região int nTopRect, // coordenada y do canto superior esquerdo da região int nRightRect, // coordenada x do canto inferior direito da região int nBottomRect // coordenada y do canto inferior direito da região );

A função CombineRgn combina duas regiões e armazena o resultado numa terceira região:

int CombineRgn( HRGN hrgnDest, // manipulador da região destino HRGN hrgnSrc1, // manipulador da região origem HRGN hrgnSrc2, // manipulador da região origem int fnCombineMode // modo de combinação );

O modo de combinação define o resultado desejado e pode ser RGN_AND, RGN_COPY, etc. No nosso exemplo foi usado o modo RGN_OR porque queremos "somar" as regiões. Veja abaixo:

Combinação
Devolvendo o controle ao gerente de mensagens

Após ter percorrido um a um os pixels do nosso bitmap e "montado" uma região que contém apenas os pixels não transparentes, vamos devolver o controle ao gerente de mensagens para que possa fazer uso desta máscara na janela.

Incorporando a região gráfica

Retornando da função grafiti, o manipulador para a região com o bitmap trabalhado, onde vazamos todos os pixels da cor que foi determinada para ser transparente, se encontra no registrador EAX. Existe uma função da API para incorporar a região gráfica a uma janela. É a SetWindowRgn:

int SetWindowRgn( HWND hWnd, // manipulador da janela que incorporará a região HRGN hRgn, // manipulador da região BOOL bRedraw // indicador de repintura );

Através desta função podemos trocar o bitmap original (aquele que nós carregamos e transferimos para o mBitmap) pelo novo bitmap obtido através da função grafiti. Fazendo a troca, termina o nosso serviço de criar a janela. Certifique-se de ter liberado o contexto de dispositivo para que outros aplicativos possam utilizá-lo. Além disso, o contexto de dispositivo de memória não é mais necessário e DEVE ser eliminado com DeleteDC.

gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL mCMMem:HDC LOCAL retang:RECT .IF uMsg == WM_CREATE invoke LoadBitmap, mInstancia, BitmapID mov mBitmap, eax invoke GetWindowDC, hWnd mov mCM, eax invoke CreateCompatibleDC, NULL mov mCMMem, eax invoke SelectObject, mCMMem, mBitmap invoke GetWindowRect, hWnd, ADDR retang invoke grafiti, mCMMem, retang.right, retang.bottom invoke SetWindowRgn, hWnd, eax, TRUE invoke DeleteDC, mCMMem ...

Pintando a janela com a máscara

Assim que uma janela é criada, ato contínuo ela começa a ser pintada. Precisamos monitorar a pintura para garantir que nosso bitmap "transparentado", obtido a duras penas, seja utilizado como máscara da nossa janela. A primeira providência é iniciar um sessão de pintura com BeginPaint e declarar uma variável local para receber o manipulador do contexto de dispositivo obtido e uma do tipo PAINTSTRUCT para receber as informações sobre a pintura. Depois precisamos obter a área retangular da janela com a função GetWindowRect e criar um contexto de dispositivo compatível com CreateCompatibleDC (se tiver dúvidas, veja novamente no tutorial "Pintando Texto"). Em seguida selecionamos o bitmap com SelectObject.

Lembre-se de que todo BeginPaint precisa terminar com um EndPaint para liberar o contexto de dispositivo para outras operações.

gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL mCM:HDC LOCAL mCMMem:HDC LOCAL retang:RECT LOCAL ps:PAINTSTRUCT .IF uMsg == WM_CREATE invoke LoadBitmap, mInstancia, BitmapID mov mBitmap, eax invoke CreateCompatibleDC, NULL mov mCMMem, eax invoke SelectObject, mCMMem, mBitmap invoke GetWindowRect, hWnd, ADDR retang invoke grafiti, mCMMem, retang.right, retang.bottom invoke SetWindowRgn, hWnd, eax, TRUE invoke DeleteDC, mCMMem .ELSEIF uMsg == WM_PAINT invoke BeginPaint, hWnd, ADDR ps invoke GetWindowRect, hWnd, ADDR retang mov mCM, eax invoke CreateCompatibleDC, NULL mov mCMMem, eax invoke SelectObject, mCMMem, hBitmap invoke EndPaint, hWnd, ADDR ps ...

Transferindo blocos de bits

Agora é só transferir as "cores dos pixels" para a área retangular da nossa janela e tchan, tchan, tchan, tchan... vestimos nossa janela com a máscara desejada. A função responsável pela transferência dos blocos de bits referentes às cores dos pixels é a BitBlt (BIT-BLock Transfer) que exige um caminhão de parâmetros:

BOOL BitBlt( HDC hdcDest, // manipulador do contexto modelo destino int nXDest, // coordenada x do canto superior esquerdo do retângulo destino int nYDest, // coordenada y do canto superior esquerdo do retângulo destino int nWidth, // largura do retângulo destino int nHeight, // altura do retângulo destino HDC hdcSrc, // manipulador do contexto modelo fonte int nXSrc, // coordenada x do canto superior esquerdo do retângulo fonte int nYSrc, // coordenada y do canto superior esquerdo do retângulo fonte DWORD dwRop // código da operação para combinação de cores ); .ELSEIF uMsg == WM_PAINT invoke BeginPaint, hWnd, ADDR retang invoke GetWindowRect, hWnd, ADDR ps mov mCM, eax invoke CreateCompatibleDC, NULL mov mCMMem, eax invoke SelectObject, mCMMem, hBitmap invoke BitBlt, mCM, 0, 0, retang.right, retang.bottom, mCMMem, 0, 0, SRCCOPY invoke DeleteDC, mCMMem invoke EndPaint, hWnd, ADDR ps ...

Como último parâmetro da função BitBlt usamos o código SRCCOPY (source copy - cópia da fonte), indicando que queremos uma simples cópia. Eliminamos mCMMem porque sempre é bom livrar um pouco de memória e... NOSSA JANELA ESTÁ PRONTA!!!

Compile o arquivo de recursos, compile e linke o código fonte. Divirta-se com o executável. É aí que aparecem alguns "senões": a janela não oferece nenhuma possibilidade visível de ser fechada e a janela não pode ser arrastada. Por enquanto, nos testes, é preciso usar Alt+F4 para fechá-la.

Permitindo o arraste da janela

O arraste da janela é efetuado mantendo o botão esquerdo do mouse pressionado enquanto se arrasta a janela. Pressionar um botão do mouse começa sempre com "abaixar", ou seja, BUTTON DOWN. Cada vez que o botão esquerdo do mouse é "abaixado", a janela recebe uma mensagem WM_LBUTTONDOWN (o L vem de LEFT, esquerdo). Vamos caçar esta mensagem e reenviá-la com os nossos parâmetros:

LRESULT SendMessage( HWND hWnd, // manipulador da janela destino UINT Msg, // mensagem a ser enviada WPARAM wParam, primeiro parâmetro da mensagem LPARAM lParam // segundo parâmetro da mensagem );

A mensagem WM_NCLBUTTONDOWN é enviada quando o usuário pressiona o botão esquerdo do mouse com o cursor FORA da área cliente da janela. Como estamos trabalhando com a área total da janela, o cursor está sempre fora da área cliente, portanto... vamos capturar a mensagem de "botão abaixado" e corrigí-la para "fora da área cliente". Para isto, precisamos conhecer melhor a mensagem WM_NCLBUTTONDOWN:

WM_NCLBUTTONDOWN nHittest = (INT) wParam; // valor do hit-test pts = MAKEPOINTS(lParam); // posição do cursor );

O hit-test (teste do ponto clicado) é um valor retornado pela DefWindowProc (o gerente de mensagens padrão do Windows) como resultado do processamento de uma mensagem WM_NCHITTEST. Esta mensagem é enviada para uma janela quando o cursor se movimenta ou quando um botão do mouse é pressionado ou solto. Nós não interceptamos esta mensagem, portanto ela entrou no processamento padrão do DefWindowProc. Dos diversos valores de retorno do processamento de uma mensagem WM_NCHITTEST, aquele que nos interessa é o HTCAPTION. Este valor indica que o clique ocorreu na barra de título da janela, ou seja, a única área da janela que permite o arrasto. Vamos "enganar" o sistema e informar que todo e qualquer clique ocorre na barra de título wink

A posição do cursor não é importante, pois não dependemos dela. Podemos simplesmente informar o valor 0 (coordenadas 0,0 da janela).

.ELSEIF uMsg == WM_LBUTTONDOWN invoke SendMessage, hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0 ...

Fechando a janela com um duplo clique

Já que estamos interceptando mensagens do mouse, vamos associar um duplo-clique do botão esquerdo do mouse com o fechamento da janela. Mas, para que a janela aceite cliques duplos, é preciso dar-lhe esta característica ao definirmos sua classe:

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD ... mov ej.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS ... invoke CreateWindowEx, NULL, ADDR NomeClasse, ADDR TituloJanela, WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, largBitmap, altBitmap, NULL, NULL, mInst, NULL ... gerenteJanela endp

Com a janela preparada, podemos interceptar a mensagem WM_LBUTTONDBLCLK (Left BUTTON DouBLe CLicK - duplo clique do botão esquerdo) e trocá-la pela mensagem WM_DESTROY, que não precisa de parâmetros específicos e fecha o aplicativo:

.ELSEIF uMsg == WM_LBUTTONDBLCLK invoke SendMessage, hWnd, WM_DESTROY, NULL, NULL ...

Serviço de faxina

Quando escrevemos um código fonte, sempre é aconselhável fazer uma revisão final para verificar se existem variáveis definidas e não inicializadas/usadas, se criamos algum objeto que não é eliminado automaticamente pelo sistema após o seu uso ou se iniciamos um processo que necessita de fechamento. É o caso do BeginPaint que precisa do EndPaint correspondente e do contexto de dispositivo que precisa ser liberado para o uso geral. Destes nós já cuidamos.

Um objeto Bitmap também precisa ser eliminado, portanto, ponha na sua lista de faxina: após um LoadBitmap SEMPRE precisa existir um DeleteObject para o(s) bitmap(s) carregado(s). Se não fizermos esta "limpeza" antes de encerrarmos o aplicativo, vai sobrar lixo e, geralmente, dos grandes.

Nós usamos a função LoadBitmap quando interceptamos a mensagem WM_CREATE e o manipulador do bitmap foi armazenado em mBitmap. Enquanto o aplicativo estiver rodando, precisamos deste bitmap para fazer a pintura da máscara da janela (interceptando WM_PAINT), portanto, não podemos eliminá-lo durante a execução. Só nos resta a alternativa de usar a DeleteObject na saída do programa, ou seja, quando interceptamos a mensagem WM_DESTROY:

.ELSEIF uMsg == WM_DESTROY invoke DeleteObject, mBitmap invoke PostQuitMessage, NULL ...

É isso aí, moçada. A novela chegou ao fim. Compilem o executável e bom divertimento biggrin

Download

Tutorial para download

Este tutorial, juntamente com o código fonte, imagens e o executável está na seção de Downloads / Tutoriais / Assembly Numaboa, mas você também pode baixá-lo aqui.

Fontes

Este tutorial se baseia no exemplo "CUSTOM WINDOWS SHAPE" escrito por mob (também conhecido como drcmda), incluído no pacote MASM32 do hutch.

Вадим Логофет Sberbankкерамическое покрытиеооо полигон одессаалександр лобановскийкп газетакупить электрическийооо отзывы