Bitmaps em AdvPL – Parte 09

Introdução

No post anterior (Bitmaps em AdvPL – Parte 08) vimos o método FlipV(), para inverter verticalmente uma imagem. Agora, vamos ver como fazer um resample ou resize da imagem usando a classe zBitmap 😀

Método Resize()

Muitas vezes precisamos mexer nas dimensões de uma imagem, aumentando ou diminuindo proporcionalmente sua dimensão, ou alterar em proporções diferentes horizontal e verticalmente a imagem. O Windows PaintBrush — entre inúmeros outros editores de imagem — possui esse recurso, permitindo o RESIZE da imagem, mantendo ou não a proporção entre os ajustes, por percentual ou por pixels.

A primeira versão do método Resize() da classe zBitmap recebe como parâmetros por hora o percentual de aumento ou redução, com base no tamanho atual (100%) horizontal e vertical, respectivamente. 100% não mexe na imagem, < 100% redux a imagem, e maior que 100% aumenta a imagem

Por exemplo, uma imagem com resolução de 400×280 pixels (colunas x linhas), ao ser reduzida horizontal e verticalmente em 50%, terá um tamanho total de 1/4 da imagem original, e ficará com 200×140 pontos. Por outro lado, se especificarmos um percentual de, por exemplo, 120%, ela será aumentada em 20% do seu tamanho original, indo para 480×336 pixels.

Como aumentar ou reduzir

A forma mais simples é criar uma nova imagem em memória, com os novos tamanhos proporcionais aos percentuais informados, e depois fazer dois loops encadeados, para definir todos os pontos da imagem nova usando como base a imagem antiga.

A sacada da operação é definir um passo proporcional de incremento de coluna e de linha para varrer a imagem antiga, contando que a imagem nova terá sempre passo 1 (todos os seus pontos serão atribuídos).

Por exemplo, um aumento para 200% — dobrar as dimensões horizontal e vertical — de uma imagem de 320×200 (colunas x linhas) vai gerar uma imagem de 640×400. Logo, criamos dois loops encadeados para varrer todas as linhas e colinas da imagem NOVA, e usamos duas variáveis sincronizadas, iniciando da primeira coluna e na primeira linha da imagem atual, usando um passo de incremento de coluna de 0,5 (obtido dividindo o tamanho horizontal atual pelo novo tamanho — 320 / 640 = 0.5), e o mesmo passo de 0,5 para o passo vertical ( 200/400 = 0.5).

Ao iniciar o processamento dos pontos das colunas da primeira linha da imagem nova, ambas começam lendo o valor da linha 1  e coluna 1 da imagem atual, e atribuindo seu valor na linha 1 coluna 1 da imagem nova. Para ir para a próxima coluna da imagem nova, incrementamos o valor da nova coluna em uma unidade, e o valor da coluna da imagem atual em 0,5, sendo assim a coluna 1.5 da imagem atual será lida. Porém, como não existe tal coluna, o acesso ao array despreza as casas decimais, e vai acessar novamente a primeira coluna da tabela atual, e atribuir seu valor na coluna 2 da nova imagem. Na próxima iteração, a coluna 1,5 da tabela atual é incrementada em 0,5, e passa a ser 2, enquanto a coluna da nova tabela será a 3. Assim, sucessivamente, a cada par de pontos nesta linha da imagem nova será atribuída com o valor de uma coluna da imagem atual, dobrando o tamanho da linha. O mesmo acontece com as linhas, usando esse mecanismo de incremento.

No caso de uma redução, fica mais simples ainda de entender o que se passa. Ao reduzir em 50% o tamanho da imagem, a nova imagem terá um looping para varrer apenas metade dos pontos de cada linha da imagem atual. Para que os pontos sejam considerados em toda a sua extensão, o passo será 2. Isto é, para cada novo ponto a ser transferido em uma coluna da nova tabela, a anterior vai pular uma coluna 😀

Essa abordagem, tanto para aumentar como parra diminuir uma imagem, é o mais simples de ser aplicado, mas tem suas vantagens e desvantagens. Por exemplo, uma imagem com uma moldura de um ponto nas bordas, e um grid de pontos com intervalo de uma linha e uma coluna, parecendo um waffle, se for reduzida por este método em 50%, vai acabar pulando todas as linhas e colunas pares, que teriam pontos brancos, e vai pegar todas as linhas ímpares — e a imagem final reduzida fica inteirinha preta.

Usando o Paintbrush, por exemplo, o algoritmo de redução tenta minimizar estas perdas, trocando alguns pontos de cor, para tentar pelo menos manter o aspecto geral da imagem anterior. Veja por exemplo as imagens abaixo:

Reducao

A primeira imagem da esquerda é um zoom em uma imagem 16×16 bits, monocromática. A segunda imagem corresponde à primeira imagem, reduzida em 50% usando o código AdvPL, e a terceira imagem é um zoom da mesma redução feita pelo PaintBrush. Ele tomou a liberdade de transformar a imagem de monocromática para Grayscale (tons de cinza), para tentar manter o aspecto da imagem original. Já o resize para aumentar a imagem comportou-se de forma igual entre o PaintBrush e a zBitmap. Os pontos da imagem foram “duplicados” horizontal e verticalmente.

Vamos ao fonte

METHOD Resize(nPctH, nPctV) CLASS ZBITMAP
Local nNewWidth
Local nNewHeight
Local nNewX := 1
Local nNewY := 1
Local nOldX
Local nOldY
Local nStepNewX 
Local nStepNewY
Local nStepOldX
Local nStepOldY
Local oNewBMP

If nPctH = 100 .and. nPctV = 100 
   Return .T.
Endif

nNewWidth := ::nWidth * (nPctH / 100) 
nNewHeight := ::nHeight * (nPctV / 100)

nStepOldX := ::nWidth / nNewWidth 
nStepNewX := 1

nStepOldY := ::nHeight / nNewHeight 
nStepNewY := 1

// Cria a nova imagem usando a própria classe
oNewBMP := zBitmap():New( nNewWidth , nNewHeight , ::nBPP )

nNewX := 1
nNewY := 1
nOldX := 1 
nOldY := 1

// Varre a imagem nova com passo 1, e a antiga 
// com o passo determinado pela proporção entre as imagens
While int(nOldY) <= ::nHeight .and. int(nNewY) <= nNewHeight
  nOldX := 1
  nNewX := 1
  While int(nOldX) <= ::nWidth .and. int(nNewX) <= nNewWidth
    oNewBMP:aMatrix[nNewY][nNewX] := ::aMatrix[nOldY][nOldX] 
    nOldX += nStepOldX
    nNewX += nStepNewX
  Enddo
  nOldY += nStepOldY
  nNewY += nStepNewY
Enddo

// Copia para a imagem atual todos os dados e propriedades
// da nova imagem gerada

::nFileSize := oNewBMP:nFileSize
::nHeight := oNewBMP:nHeight
::nWidth := oNewBMP:nWidth
::aMatrix := aClone(oNewBMP:aMatrix)
::aClipBoard := aClone(oNewBMP:aClipBoard)
::aColors := aClone(oNewBMP:aColors)
::cFormat := oNewBMP:cFormat
::nOffSet := oNewBMP:nOffSet
::nRawData := oNewBMP:nRawData
::nRowSize := oNewBMP:nRowSize
::nHeadSize := oNewBMP:nHeadSize
::nCompress := oNewBMP:nCompress
::nColorPlanes := oNewBMP:nColorPlanes
::nHRes := oNewBMP:nHRes
::nVRes := oNewBMP:nVRes
::nColorPal := oNewBMP:nColorPal
::nImpColors := oNewBMP:nImpColors
::nFRColor := oNewBMP:nFRColor
::nBgColor := oNewBMP:nBgColor

// joga a nova imagem de uso temporário fora e retorna
freeobj(oNewBMP)

Return .T.

Com este recurso na classe zBitmap, você pode por exemplo criar um QRCode ou um zBarCode128, originalmente com o menor tamanho possível (1 pixel por módulo), e então utilizar o método Resize() para criar uma imagem final com mais pontos, para ser utilizada para visualização em tela como para impressão.

É recomendado fortemente que, para que não hajam distorções no aumento da imagem, que as proporções sejam sempre múltiplas de 100% — 200%, dobra o tamanho, 300% triplica, e assim por diante. Caso a proporção de aumento seja um fator multiplicativo quebrado, como estamos partindo de uma imagem onde cada pixel conta, pode haver alteração da proporção entre as barras de um código, o que pode levar a um leitor simplesmente não conseguir ler corretamente a imagem impressa.

Conclusão

Para cada cenário existe uma solução adequada. Se esta solução se encaixa no seu cenário, baixe a ZLIB e seja feliz !!!

E desejo novamente a todos TERABYTES DE SUCESSO !!!

Referências

 

 

Deixe um comentário