Imagens – Resolução, DPI e Escala

Introdução

No post anterior (Imagens, cores, resolução e impressão) vimos alguns pontos interessantes sobre resolução de imagens em pontos (ou pixels), e que o DPI ( Dots Per Inch ) é a capacidade de pontos por polegada de um determinado dispositivo — vídeo ou impressora — relacionada ao tamanho de cada ponto a imagem no dispositivo. Agora, vêm a pergunta de 1 milhão … por quê uma imagem PNG ou JPG por exemplo possui ou pode possuir uma informação de DPI ?! E qual é a relevância disso ?!

Elementar, meu caro Watson

A informação de DPI de uma imagem existe para indicar um tamanho de representação, meramente informativo. Isso mesmo, DPI na imagem na prática é dummy. Eu posso criar duas imagens de mesma resolução de pontos, e simplesmente salvar esta imagem duas vezes, uma com um header informando 72 DPI, e outra informando 144 DPI. A imagem em si vai ser a mesma, o mesmo tamanho, a resolução em pixels, e a mesma representação gráfica e qualidade.

Eu usei um telefone celular com uma câmera de alta resolução em modo “square” (quadrado) para tirar uma foto de uma furadeira junto de um esquadro com medidas em polegadas e centímetros. Enviei a imagem original — sem redução de tamanho — do celular para meu e-mail, e recebi um arquivo JPEG de 1,67 MB, com resolução de 2448 x 2448 pontos, informando 72 DPI, veja a imagem abaixo:

Furadeira1_JPEG

Ao abrir a imagem no PaintBrush e consultar as suas propriedades, constam a resolução de pontos (2448 x 2448) e DPI (72)

furadeira1_propriedades

A informação de DPI dentro da imagem serve apenas para programas editores de imagem e diagramação, onde você trabalha com um lay-out de visão final de impressão, quando ao inserir a mesma imagem com mesma resolução de pontos mas com DPIs diferentes, ele vai mostrar a mesma imagem dimensionada com tamanhos diferentes, e você ajusta o tamanho que melhor se enquadra para o que você precisa.

Escala

Quem já teve um guia rodoviário ou viu um mapa impresso, ou mesmo usou um Google Maps ou Google Earth, deve saber o que é isso. Trata-se de uma relação entre o tamanho do mapa e do tamanho real que o mapa representa. Eu já comi bola ao ler um guia rodoviário, achei que estaria no meu destino perto da hora do almoço, mas cheguei na hora da janta. Todas as páginas no mapa eram escala 1:18 (1 cm = 18 Km), exceto o mapa do Rio Grande do Sul, que era escala 1:27 — Eu deveria percorrer no mapa uns 25 centímetros, o equivalente a 450 Km … mas como eu usei a escala errada, na verdade eu precorri 675 Km.

Como uma imagem qualquer pode ter dimensões de muitos pixels, maiores que a resolução do monitor de vídeo — por exemplo a imagem que eu tirei com o celular. Minha resolução de vídeo é 1920 x 1080, com 25% de aumento — não cabe a imagem inteira. Ao abrir a imagem com um programa de visualização, veja o destaque em vermelho:

Furadeira_view

Reparem que a imagem foi mostrada por inteiro na tela, porém no campo em vermelho, o aplicativo mostrou que a imagem foi reduzida para 30% da sua resolução original para caber na tela. Se pegarmos esta imagem e alterarmos a visualização para 100% , veremos apenas uma parte da imagem, e usar as barras de scroll para ver as demais áreas:

Furadeira_view_100

Conclusão

Programação envolve também conhecimentos de informações adicionais, não apenas comandos e funções. Espero que estas informações lhes sejam úteis, e desejo a todos novamente TERABYTES DE SUCESSO !!!

Referências

 

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

 

 

Imagens, cores, resolução e impressão

Introdução

Quando comecei as implementações de imagens em AdvPL, partindo da leitura da paleta de cores e do mapa de pontos da imagem, a parte de cores RGB / RGBA e CMYK deu um nó na minha cabeça … cores e tintas são diferentes ? Como assim ?! Agora que eu entendi a jabiraca, vamos ver o que eles têm de diferente, e também um pouco sobre a resolução de imagens para visualização e para impressão.

Luzes e Pigmentos

RGB é uma forma de representação de cores ou pontos luminosos, enquanto CMYK é a representação de tintas que refletem as cores, e as regras de mistura de ambas são diferentes.

Quanto misturamos tintas ou pigmentos de coloração diferente, obtemos novas cores. Normalmente usamos um grupo de cores chamadas primárias (por exemplo azul, amarelo e vermelho), e a partir da mistura delas — na mesma proporção — obtemos cores secundárias e terciárias. E, variando as proporções das cores, obtemos cores e tons diferenciados.

Quando misturamos luzes de cores diferentes, por exemplo vermelho, verde e azul, obtemos luzes com novas cores, onde ao mudar a intensidade de cada cor, obtemos novas cores e variações. A diferença básica entre ambas é justamente o comportamento das misturas.

RGB e Luzes

Uma imagem composta de pontos luminosos usando apenas as cores vermelho, verde e azul, com variações de intensidade de 0 ( nenhuma ) a 255, permite a criação de 16.777.216 combinações diferentes, onde as diferenças são tão sutis entre combinações muito próximas passam despercebido pelo olho humano. A cor preta, por exemplo, obtém-se com 0 de intensidade nas cores RGB. Já a cor branca é obtida com a intensidade máxima das três cores. Chamamos esse comportamento de mistura de “aditivo”.

CMYK e Pigmentos

Para imprimir uma imagem, também podemos combinar pigmentos para obter novas cores, as impressoras a Jato de Tinta usam isso muito bem, com apenas 3 cartuchos coloridos elas combinam as cores em proporções específicas para imprimir uma fotografia. Partindo das cores vermelho, azul e amarelo, misturando proporções diferentes destas cores, obtemos novas cores e tons. Especificamente, CMYK é um processo de obtenção de pigmentos partido das cores Ciano, Magenta, amarelo (Yellow), e preto, usada em Offset e impressões gráficas.

Curiosidade: A letra “K” da sigla não vêm da cor preta (Black) em inglês, mas sim da palavra “KEY”, pois ela é considerada uma cor “chave”, por não ser uma cor pura. 

Porém, um pigmento não emite luz, ele reflete apenas uma parte do espectro luminoso. A cor branca é justamente a mistura das luzes do espectro luminoso. Logo, ao iluminarmos uma folha com um desenho usando luz branca, a tinta vermelha absorve as demais cores, e reflete apenas vermelho, logo cada pigmento reflete uma parte do espectro luminoso. Quando misturamos azul com amarelo, temos a cor verde, onde o espectro refletido diminuiu. Se misturarmos vermelho, amarelo e azul na mesma proporção, teremos a cor preta, onde não há reflexão de nenhuma cor do espectro visível. Este comportamento chama-se “subtrativo” — quanto mais pigmentos de cores diferentes acrescentamos, menos luz será refletida. 😀

Resolução de visualização

Quando falamos de uma imagem representada em um monitor de vídeo, estamos limitados a uma resolução de grade de pontos, em colunas x linhas, e um limite de cores por ponto. Por exemplo, um monitor VGA de um determinado tamanho em polegadas –correspondente a medida da diagonal do monitor — pode ter uma resolução de 800 (colunas) x 600 (linhas) x 256 cores.

Uma imagem no computador representada em um mapa de pontos possui comprimento e altura definido em número de pontos. Logo, não importa se o seu monitor VGA de 800×600 têm 14 ou 21 polegadas, uma imagem de 800×600 pontos vai ocupar a tela inteira, usando 480 mil pontos. Se você usar um aplicativo para reduzir a imagem em 50% de comprimento e 50 % de altura, teremos uma nova imagem agora de 400×300 pontos, que ocupa apenas 1/4 da tela, e diminuiu de 480 para 120 mil pontos — quatro vezes.

Isso impacta por exemplo em, na imagem de tamanho original, você percebe pequenos detalhes, mas ao jogar 360 mil pontos fora, você ainda vê uma imagem, mas os detalhes sumiram, algumas coisas podem ficar meio “quadradas”.

Da mesma forma, uma imagem de baixa resolução em linhas e colunas de pontos, por exemplo uma imagem originalmente representada em 400×300 pontos, se for aumentada em 100% (dobrar a resolução de linhas e colunas), o aplicativo apenas vai reescrever a imagem usando a proporção 1 ponto da imagem original = 4 pontos da nova imagem. E, alguns aplicativos gráficos mais elaborados podem minimizar o efeito “ATARI” da imagem trocando as cores de alguns pontos intermediários baseado na proximidade com os pontos adjacentes, mas você vai ver que a imagem fica meio “borrada”.

Resolução de Impressão

Quando vamos imprimir uma imagem, devemos considerar alguns fatores característicos da impressão. Um deles por exemplo, é que a impressão partindo da criação de cores pela mistura de pigmentos normalmente precisa de mais pontos de cores misturadas para ter uma aparência parecida da representação da imagem no vídeo. Uma impressora normalmente têm a sua resolução expressa em DPI (Dots Per Inch — ou pontos por polegada). Os modelos de impressoras do tipo “Jato de Tinta” têm este nome, pois as tintas são misturadas sobre o papel com micro-jatos, para formar as cores dos pontos desejadas.

Relação entre as resoluções

Como vimos, temos a resolução da imagem, da tela e da impressora. A imagem tem um tamanho em pontos, colunas x linhas, e uma quantidade de cores para cada ponto. A visualização da imagem na tela depende da resolução da tela e do tamanho desejado da imagem na tela, a versão impressa da imagem também depende da resolução da impressora e do tamanho desejado da imagem no papel.

Trocando em miúdos, uma imagem com uma determinada resolução qualquer pode ser mostrada ou impressa em dispositivos diferentes, com resoluções diferentes, porém para se obter o tamanho desejado para visualização ou impressão em uma determinada resolução, duas coisas podem acontecer:

  • Se a imagem tem um número de pontos maior do que cabe no dispositivo para ela ficar do tamanho desejado, pontos serão jogados fora pra fazer a imagem caber.
  • Se a imagem tem um número de pontos menor do que o necessário para preencher o espaço desejado, a aplicação terá que “fabricar” mais pontos na imagem usando os pontos originais para representá-la.

Estes métodos de transformação entre resoluções são também conhecidos como “resampling” e “resize“.  Normalmente um resampling é melhor que um resize, por isso é interessante você tirar fotos com uma resolução maior, pois se você quiser a impressão em um tamanho grande, a imagem não perde a qualidade, mesmo jogando alguns pontos fora, enquanto partir de uma imagem de baixa resolução para uma impressão maior, vai exigir que mais pontos sejam “fabricados” para atender a resolução, ou você vai ter que usar uma resolução de impressão mais baixa, e a imagem fica borrada ou “quadradinha”.

Conclusão

Para quem tem mais curiosidade sobre esse assunto, recomendo acessar os links de referência no final do post.

Espero que estas informações lhes sejam úteis, e desejo a todos TERABYTES DE SUCESSO !!! 😀

 

Referências

 

 

Imagens PNG em AdvPL – Parte 04

Introdução

No post anterior (Imagens PNG em AdvPL – Parte 03) vimos a leitura de um PNG monocromático. Agora, vamos ver como salvar a imagem em mapa de bits monocromática em um arquivo PNG, implementando um método novo na classe zBitmap 😀

Método SaveToPNG()

Mais simples que a leitura, por hora o método gera um PNG menor ainda, pois não coloca nenhum Chunk não-crítico (irrelevante para este cenário). Apenas os 4 principais, nesta ordem: IHDR, PLTE, IDAT e IEND.

// ---------------------------------------------------
// Salva a imagem em disco como PNG 
// Por hora suporte apenas monocromatico

METHOD SaveToPNG(cFile) CLASS ZBITMAP
Local aPng := Array(14)
Local nH
Local nI, nL , nC
Local cBuffer , nDataSize , cData , cBits, nByte

aPng[PNG_WIDTH] := ::nWidth
aPng[PNG_HEIGHT] := ::nHeight
aPng[PNG_BIT_DEPHT] := 1
aPng[PNG_COLOR_TYPE] := 3
aPng[PNG_COMPRESSION] := 0
aPng[PNG_FILTER] := 0
aPng[PNG_INTERLACE] := 0 
aPng[PNG_SRGB] := 0
aPng[PNG_GAMA] := 45455
aPng[PNG_PIXELPERUNIT_X] := 4724
aPng[PNG_PIXELPERUNIT_Y] := 4724
aPng[PNG_PIXEL_UNIT] := 1

nH := fCreate(cFile)

If nH == -1
  ::cError := "File Create Error - FERROR = "+cValToChar(ferror())
  Return .F.
Endif

// Inicia com o header PNG
fWrite(nH , PNG_HEADER)

// Monta o Chunk IHDR 13 bytes
cData := ''
cData += nToBin4(aPng[PNG_WIDTH])
cData += nToBin4(aPng[PNG_HEIGHT])
cData += chr(aPng[PNG_BIT_DEPHT])
cData += chr(aPng[PNG_COLOR_TYPE])
cData += chr(aPng[PNG_COMPRESSION])
cData += chr(aPng[PNG_FILTER])
cData += chr(aPng[PNG_INTERLACE])

// Salva o Chunk IHDR
PNGSaveChunk(nH,"IHDR",cData)

// Monta o Chunk PLTE
cData := ''
For nI := 1 to len(::aColors)
  // Monta o Chucn RGB partindo da matriz BGRA 
  cData += chr(::aColors[nI][3]) + Chr(::aColors[nI][2]) + chr(::aColors[nI][1]) 
Next

PNGSaveChunk(nH,"PLTE",cData)

// Monta o chunk IDAT -- por enquanto apenas um

cBuffer := ''
For nL := 1 to ::nHeight
  cBits := '00000000'
  For nC := 1 to ::nWidth
    cBits += STR(::aMAtrix[nL][nC],1)
  Next
  while len(cBits)%8 > 0 
    cBits += '1'
  Enddo 
  while !empty(cBits) 
    BIT8TON(substr(cBits,1,8),nByte)
    cBuffer += chr(nByte)
    cBits := substr(cBits,9)
  Enddo 
Next

// Comprime o buffer 
cData := ''
nDataSize := 0
compress(@cData,@nDataSize,cBuffer,len(cBuffer))

// Salva o buffer comprimido 
PNGSaveChunk(nH,"IDAT",cData)

// Salva o final da imagem
PNGSaveChunk(nH,"IEND","")
fClose(nH)
Return .T.

STATIC Function PNGSaveChunk(nH , cType,cData)
Local nSize :=  len(cData)
Local nCRC := PNGCRC(cType+cData)
fWrite( nH , nToBin4(nSize) + cType + cData + nToBin4(nCRC) )
Return

Como todos os chunks têm o mesmo formato — Size, Type, Data e CRC — a função de gravação recebe os dados do chunk a ser gravado e se vira pra gerar o CRC e salvar tudo na ordem certa.

Exemplo de uso

Usei o fonte de testes da classe  zBarCode128, e após gerar a imagem, apenas mandei ela ser salva em PNG.

#include 'protheus.ch'
#include 'zlib.ch'

User Function PocBAR128()
Local oBar := zBarCode128():New()
Local aSeq, oBmp
// Gera a sequencia de valores em array
aSeq := oBar:Generate('https://siga0984.wordpress.com')
// Monta o bitmap usando os valores
oBmp := oBar:BuildBmp(aSeq)
// Salva a imagem em disco como CMP e PNG
oBmp:SaveToBMP('\barcode.bmp')
oBmp:SaveToPNG('\barcode.png')
Return

No teste acima, a imagem em BMP  — bem pequena, apenas um pixel por módulo, 374×40 pixels — ocupa em disco 1982 bytes, contra apenas 156 bytes da imagem salva em PNG. Veja o dump binário do arquivo da imagem abaixo:

pngsave2

E a imagem, claro ..rs…

barcode

Conclusão

Sim, ainda falta implementar o suporte a imagens PNG coloridas, que deve ser muito próximo da implementação do BMP — exceto pela parte de trabalhar com mais de um Chunk IDAT — mas isso já é assunto para outro post !!!

Agradeço desde já a audiência, espero que estes fontes lhes sejam úteis, e desejo novamente a todos TERABYTES DE SUCESSO !!! 

 

 

Imagens PNG em AdvPL – Parte 03

Introdução

No post anterior (Imagens PNG em AdvPL – Parte 02) vimos apenas o cálculo do CRC dos Chunks do arquivo PNG. Agora vamos ver como colar de forma elegante — e passar a limpo — aquele POC do primeiro post (Imagens PNG em AdvPL) na classe zBitmap.

Classe zBitmap

Originalmente criada com o objetivo de criar e manipular arquivos com o formato Windows BMP, na prática uma imagem qualquer terá uma representação gráfica final em um grid de linhas e colunas de pontos coloridos — ou mapa de bits. Logo, nada impede de acrescentar o suporte a leitura e gravação em outros formatos de imagem, mantendo a representação em memória original do zBitmap. A zLib completa está disponível para download no GITHUB via este link.

Implementando a leitura

Como tudo em informática, existem formas de implementar este suporte, desde o recomendado pelas boas práticas de orientação a objeto — criar uma classe base de imagem abstrata e implementar os suportes aos formatos — ou a forma mais prática para a necessidade — implementa na classe atual um LoadFromPNG() e um SaveToPNG(), usando algumas static functions comuns a ambos os métodos. Neste momento e para este caso vou optar pela segunda alternativa.

O método ainda têm buracos, faltam validações, ainda está em desenvolvimento, mas já é funcional — Para PNG monocromático com menos de 32 mil pontos 😀

#define PNG_WIDTH 1
#define PNG_HEIGHT 2
#define PNG_BIT_DEPHT 3
#define PNG_COLOR_TYPE 4
#define PNG_COMPRESSION 5
#define PNG_FILTER 6
#define PNG_INTERLACE 7
#define PNG_SRGB 8
#define PNG_GAMA 9
#define PNG_PIXELPERUNIT_X 10
#define PNG_PIXELPERUNIT_Y 11
#define PNG_PIXEL_UNIT 12
#define PNG_PALLETE 13
#define PNG_IMAGEDATA 14

#define PNG_HEADER chr(137)+chr(80)+chr(78)+chr(71)+chr(13)+chr(10)+chr(26)+chr(10)

METHOD LoadFromPNG(cFile) CLASS ZBITMAP
Local nH,cBuffer := ''
Local aPng := Array(14)
Local iTam , cData , nCRC // Dados do Chunk
Local cBufferOut := ''
Local nLenghtOut 
Local nColSize , cRowBuffer
Local nColPos,cBits
Local nL , nC
Local aRow := {}

::cFileName := cFile

nH := fopen(cFile)

IF nH == -1
  ::cError := "File Open Error - FERROR = "+cValToChar(Ferror())
  Return .F.
Endif

// Determina o tamanho e le o arquivo inteiro 
::nFileSize := fSeek(nH,0,2)
fSeek(nH,0)
fREad(nH,@cBuffer,::nFileSize)
fClose(nH)

IF !(Left(cBuffer,8) == PNG_HEADER )
  ::cError := "File is NOT A PNG"
  Return .F.
Endif

// Corta o header fora
cBuffer := substr(cBuffer,9)

// Varre o resto do buffer 
while len(cBuffer) > 0

  // Tamanho dos dados do chunk
  iTam := Bin4toN(left(cBuffer,4))
  cBuffer := substr(cBuffer,5)

  // Tipo do Chunk
  cType := left(cBuffer,4)
  cBuffer := substr(cBuffer,5)

  If iTam > 0
    cData := left(cBuffer,iTam)
    cBuffer := substr(cBuffer,iTam+1)
  Else
    cData := ''
  Endif

  nCRC := Bin4toN(left(cBuffer,4))
  cBuffer := substr(cBuffer,5)

  nChkCRC := PNGCRC(cType+cData)

  If Upper(cType) == 'IHDR'

    /*
    Width: 4 bytes
    Height: 4 bytes
    Bit depth: 1 byte
    Color type: 1 byte
    Compression method: 1 byte
    Filter method: 1 byte
    Interlace method: 1 byte
    */

    aPng[PNG_WIDTH] := Bin4toN(substr(cData,1,4))
    aPng[PNG_HEIGHT] := Bin4toN(substr(cData,5,4))
    aPng[PNG_BIT_DEPHT] := asc(substr(cData,9,1))
    aPng[PNG_COLOR_TYPE] := asc(substr(cData,10,1))
    aPng[PNG_COMPRESSION] := asc(substr(cData,11,1))
    aPng[PNG_FILTER] := asc(substr(cData,12,1))
    aPng[PNG_INTERLACE] := asc(substr(cData,13,1))

    // Por enqianto aproveita apenas comprimento e altura
    ::nWidth := aPng[PNG_WIDTH]
    ::nHeight := aPng[PNG_HEIGHT]

  ElseIF Upper(cType) == 'SRGB'

    /*
    0: Perceptual
    1: Relative colorimetric
    2: Saturation
    3: Absolute colorimetric
   */

    aPNG[PNG_SRGB] := asc(substr(cData,1,1))

  ElseIF Upper(cType) == 'GAMA'

    // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
    // For example, a gamma of 1/2.2 would be stored as 45455.
    aPng[PNG_GAMA] := Bin4toN(substr(cData,1,4))

  ElseIF Upper(cType) == 'PLTE'

  // Paleta de cores sequencias de 3 bytes RGB

    ::aColors := {}
    aPng[PNG_PALLETE] := {}
 
    While len(cData) > 0 
      nRed := asc(substr(cData,1,1))
      nGreen := asc(substr(cData,2,1))
      nBlue := asc(substr(cData,3,1))
      cData := substr(cData,4)

      // Paleta do BITMAP : BGRA
      aadd(::aColors,{nBlue,nGreen,nRed,0}) // BGRA
    Enddo

  ElseIF Upper(cType) == 'PHYS'

    /*
    Pixels per unit, X axis: 4 bytes (unsigned integer)
    Pixels per unit, Y axis: 4 bytes (unsigned integer)
    Unit specifier: 1 byte
    */
 
    aPng[PNG_PIXELPERUNIT_X] := Bin4toN(substr(cData,1,4))
    aPng[PNG_PIXELPERUNIT_Y] := Bin4toN(substr(cData,5,4))
    aPng[PNG_PIXEL_UNIT] := asc(substr(cData,9,1))

  ElseIF Upper(cType) == 'IDAT'

    // por hora chumbado monocromatico 
    ::nBPP := 1 
    ::nBgColor := 1

    cBufferOut := ''
    nLenghtOut := 32*1024
    UnCompress( @cBufferOut , @nLenghtOut , cData, len(cData) )

    nColSize := nLenghtOut / aPng[PNG_HEIGHT]

    // Inicializa BMP com fundo branco
    ::aMatrix := {}
    For nC := 1 to ::nWidth
      aadd(aRow,::nBgColor)
    Next
    For nL := 1 to ::nHeight
      aadd(::aMatrix,aClone(aRow))
    Next

    // Mastiga o buffer binario alimentando a matrix do Bitmap
    For nL := 1 to aPng[PNG_HEIGHT]
      cRowBuffer := substr(cBufferOut,1,nColSize)
      cBufferOut := substr(cBufferOut,nColSize+1)
      nColPos := 1
      For nC := 2 to nColSize
        cBits := NTOBIT8( asc(substr(cRowBuffer,nC,1)) )
        While len(cBits) > 0
          IF nColPos < aPng[PNG_WIDTH]
            ::aMatrix[nL,nColPos] := val(left(cBits,1))
          Endif
          nColPos++
          cBits := substr(cBits,2)
        Enddo
      Next
    Next

  ElseIF Upper(cType) == 'IEND'

    // Fim do arquivo 
    EXIT

  Else
    Conout("WARNING - LoadFromPNG - Ignored Chunk ["+cType+"]")
  Endif

Enddo

Return .T.

Considerações

Algumas informações adicionais eu acabei guardando em um array local aPNG, reservado para implementações futuras. O CRC na leitura já está sendo calculado e consistido, funciona certinho !! Com este método, você consegue criar um zBitmap() na memoria, ler um PNG, mexer nele com os métodos disponíveis na classe, e por hora salvá-lo como BMP. No próximo post, sai a implementação do método de gravação para PNG 😀

Agradeço a todos pela audiência, e lhes desejo TERABYTES DE SUCESSO !!!

Referências

 

 

 

 

 

 

Imagens PNG em AdvPL – Parte 02

Introdução

No post anterior (Imagens PNG em AdvPL) vimos a primeira prova de conceito de leitura de um PNG monocromático. Agora, vamos ver como calcular o CRC de cada chunk, partindo de um fonte em C — e convertendo ele para AdvPL. Recomendo fortemente a leitura do post anterior como base para esta publicação.

CRC

CRC — do inglês Cyclic redundancy check — é um código de verificação de erro normalmente usado em redes digitais e dispositivos de armazenamento para detectar alterações acidentais nos dados. Trata-se da aplicação repetida (ou cíclica) de uma divisão polinomial para uma sequencia de dados, onde o resultado é acrescentado junto dos dados.

A especificação do CRC parece complexa, mas a sua aplicação fica bem mais fácil quando temos em mãos um exemplo eficiente de cálculo, mesmo que esteja em outra linguagem. Como foi o caso do CRC aplicado a cada chunk do arquivo PNG. Vejamos um código em C, dado como exemplo de cálculo do CRC de um determinado buffer. — código extraído do site http://www.libpng.org, vide referências no final do post.

/* Table of CRCs of all 8-bit messages. */
   unsigned long crc_table[256];
   
   /* Flag: has the table been computed? Initially false. */
   int crc_table_computed = 0;
   
   /* Make the table for a fast CRC. */
   void make_crc_table(void)
   {
     unsigned long c;
     int n, k;
   
     for (n = 0; n < 256; n++) {
       c = (unsigned long) n;
       for (k = 0; k < 8; k++) {
         if (c & 1)
           c = 0xedb88320L ^ (c >> 1);
         else
           c = c >> 1;
       }
       crc_table[n] = c;
     }
     crc_table_computed = 1;
   }
   
   /* Update a running CRC with the bytes buf[0..len-1]--the CRC
      should be initialized to all 1's, and the transmitted value
      is the 1's complement of the final running CRC (see the
      crc() routine below)). */
   
   unsigned long update_crc(unsigned long crc, unsigned char *buf,
                            int len)
   {
     unsigned long c = crc;
     int n;
   
     if (!crc_table_computed)
       make_crc_table();
     for (n = 0; n < len; n++) {
       c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
     }
     return c;
   }
   
   /* Return the CRC of the bytes buf[0..len-1]. */
   unsigned long crc(unsigned char *buf, int len)
   {
     return update_crc(0xffffffffL, buf, len) ^ 0xffffffffL;
   }

Então, como assim ?!

Entre todas as diferenças da linguagem C para o AdvPL, de sintaxe a tipos de dados, isso realmente não importa. O que importa é eu ter uma forma de fazer a mesma coisa, os mesmos cálculos em outra linguagem. Vamos por partes :

Primeiro, o programa em C cria um array com 256 posições, como uma tabela pré-calculada de CRC, armazenado no array global crc_table, um array de números inteiros de 32 bits com 256 posições — do 0 ao 255.

Uma parte do código cria o conteúdo para estes elementos — usando a função make_crc_table() — realizando uma série de operações de comparação binária AND ( & ) , XOR ( ^ ) e RIGHT BIT SHIFT ( >> ). Depois, a geração do CRC inicializa o número com os 32 bits “ligados”, e aplica o resultado da fórmula polinomial pré-calculada para cada byte da sequência, gerando o CRC. Agora, para converter isso pro AdvPL, vamos por partes

Operações em C e funções equivalentes em AdvPL

Usamos array dinâmico em AdvPL, e podemos armazená-lo dentro de uma variável STATIC no AdvPL para utilização posterior. A operação binária AND ( & ) pode ser feita com a função AdvPL NAND(), o XOR ( ^ ) pode ser feito com a função AdvPL NXOR(), o RIGHT BIT SHIFT equivale a dividir o número decimal por dois considerando apenas a parte inteira do resultado — INT(X/2). E, todos os números constantes em hexadecimal no código podem ser convertidos para números constantes em base decimal no AdvPL.

Geração da tabela de CRC — 256 elementos

Como podemos usar esta tabela várias vezes dentro do mesmo programa, para cada Chunk — de todos os tipos — seja para verificar a integridade do arquivo na abertura como para criar um PNG — vamos armazená-lo em uma variável STATIC em AdvPL, que persiste pela vida do processo dentro do fonte atual.

STATIC aCRCTable := CRCTable()

Agora, dentro do mesmo fonte, vamos criar a função de carga como uma STATIC FUNCTION

STATIC Function CRCTable()
Local aTable := {}
Local nI, nJ , C

For nI := 0 to 255
  C := nI
  For nJ := 0 to 7
    IF nAnd(C,1)
      C := nXor( 3988292384 , Int( C / 2 ) )
    Else 
      C := Int( C / 2 )
    Endif
  Next
  aadd(aTable,C)
Next
Return aTable

Com isso, na carga desse fonte a tabela de constantes será gerada e mantida na memória até o final da thread/processo. Agora, vamos fazer a função que recebe uma String e calcula o CRC.

STATIC Function PNGCRC(cBuffer)
Local C := 4294967295 // 0xFFFFFFFF
Local nI , nASC , nIndex

For nI := 1 to len(cBuffer)
  nASC := asc(substr(cBuffer,nI,1))
  nIndex := nAnd ( nXor( C , nASC ) , 255 ) // 0xff
  C := nXor ( aCRCTable[nIndex+1] , INT( C / 256) ) // C >> 8
Next

Return nXor( C , 4294967295 ) // 0xFFFFFFFF

As diferenças adicionais do código original em C são por conta de usar um array dinâmico em AdvPL, e justamente o Array em AdvPL é base 1 — o primeiro elemento é o índice 1, enquanto em C o primeiro elemento é índice 0 (zero). Logo, quando usamos a tabela, calculamos o valor da posição da tabela da mesma forma que na linguagem C, porém ao acessar o elemento da tabela, somamos 1 no índice. Voilá 😀

Não precisamos do flag “crc_table_computed”, pois a carga do fonte pela thread é feita apenas uma vez, e a geração da tabela é feita automaticamente e apenas uma vez por thread/processo, e não precisamos de duas funções (CRC e UPDATE_CRC) só para encapsular o processo, basta uma função inicializando o CRC com o valor cheio em 32 bits 4294967295 (0xFFFFFFFF em hexadecimal) e fazendo o NXOR() no retorno .Com isso, temos uma forma muito rápida de calcular ou validar o CRC dos Chunks do arquivo PNG.

Conclusão

Você pode fazer a mesma coisa em linguagens diferentes, de jeitos diferentes, e chegar ao mesmo resultado. No próximo post da sequência, vamos ver como encaixar o suporte ao formato PNG de forma elegante na classe de imagens zBitmap 😀

Agradeço novamente a audiência, e desejo a todos TERABYTES DE SUCESSO !!!

Referências

 

Imagens PNG em AdvPL

Introdução

Em posts anteriores, vimos a implementação da classe zBitMap, que permite criar e manipular arquivos no formato BMP — começando com Bitmaps em AdvPL – Parte 01. Hoje vamos iniciar a extensão para suporte a arquivos PNG — começando pelos monocromáticos. 😀

Especificação do formato PNG

PNG — Portable Network Graphics — é um formato de codificação de imagens lossless — sem perda de qualidade — criado como um substituto para o formato GIF, que possui algoritmos patenteados. Internamente, o arquivo possui uma assinatura fixa, seguida por “chunks” (ou pedaços, partes) nomeados, sequenciais e de formato pré-definido, onde os dados da imagem são compactados usando o algoritmo zip/deflate. Para ver todos os detalhes, acesse os links de referência no final do post.

Arroz com feijão

Começando pelo básico, a representação PNG permite muitas definições, e optimizações, mas uma imagem monocromática é praticamente a mesma de um BITMAP, com a diferença de estar compactada.

Cada pedaço — chunk — do arquivo após o header/assinatura PNG possui o seguinte formato:

  • 4 bytes – Tamanho N da área de dados do Chunk ( 32 bits big-endian )
  • 4 Bytes – Tipo do chunck, ASCII
  • N byte(s) de dados – Cada tipo possui a sua forma de interpretação
  • 4 Bytes – CRC ( 32 bits big-endian )

o CRC é calculado sobre os bytes anteriores do chunk, incluindo o tipo e os dados, mas não incluindo os 4 primeiros bytes — tamanho — usando o mesmo algoritmo.

Os chunks principais, também chamados de “Critical Chunks” são:

  • IHDR Image header
  • PLTE Palette
  • IDAT Image data
  • IEND Image trailer

De forma similar ao BMP, a área de dados (descompactada) corresponde a sequência de pontos que compõe a imagem, codificando as linhas da imagem (de cima para baixo) onde cada ponto indica uma cor da Pallete utilizada. Um PNG monocromático contém apenas duas cores RGB : 000000 (preto)  e FFFFFF ( branco), logo o mapa de bits da imagem usa um bit para cada cor (0=preto,1=branco).

A leitura da imagem é feita sequencialmente, chunk a chunk, pegando as informações relevantes de cada chunk e necessárias para avaliar seus conteúdos. Por exemplo, para analisar e entender o formato do chuck IDAT, eu preciso saber as dimensões da imagem e profundidade de bits, que estão no chunk IHDR.

Chunk IHDR

Contém as definições da imagem, tamanho fixo de 13 bytes, no seguinte formato:

  • Comprimento da imagem (pixels): 4 bytes
  • Altura da imagem (pixels) : 4 bytes
  • Profundidade de Bits: 1 byte
  • Typo de cor: 1 byte
  • Método de compressão: 1 byte
  • Filtro: 1 byte
  • Entrelaçamento: 1 byte

Chuck PLTE

Contém o mapa de cores da imagem, representados em uma sequência de blocos de três bytes, onde cada byte contém respectivamente as cores R, G e B (Red, Gray and Blue). Em um PNG monocromático, esta lista contém apenas 6 bytes: 00 00 00 FF FF FF 

Chunk IDAT

Contém a representação da imagem em mapa de bits, compactada. A leitura desse buffer deve levar em conta as propriedades do IHDR. Uma imagem pode conter mais de um chunk de dados, colocados de forma sequencial. Imagens pequenas contém apenas um chunk, existe uma limitação de tamanho do chunk definida para fins de compressão. DE forma bem simples, um chunk não deve conter mais do que 32 KB de dados não comprimidos.

Felizmente, as funções compress() e uncompress() do AdvPL utilizam o algoritmo glib/deflate, o que tornou possível sem maiores dores implementar a codificação e decodificação desse chunk.

Chunk IEND

Apenas indica o final da imagem 😛

POC

Um POC é uma prova de conceito — do inglês Proof Of Concept — e foi isso que eu fiz pra confirmar se dava pra ler uma imagem PNG e extrair dela o mapa de bits da imagem. Trata-se de um programa de testes — normalmente sem maiores tratamentos — criado para ver se algo é possível de ser feito. Segue abaixo o POC de leitura de um arquivo PNG monocromático. Neste final de semana a classe zBitmap vai ganhar mais métodos 😀

USER Function LePNG()
Local cFile := '\teste.png'
Local nH,cBuffer := ''

nH := fopen(cFile)
IF nH == -1
	conout("Erro ao abrir arquivo - FERROR = "+cValToChar(Ferror()))
	Return
Endif

nTam := FSeek(Nh,0,2)
fSeek(nh,0)
fREad(nH,@cBuffer,nTam)
fClose(nH)
//Conout(hexstrdump(cBuffer))

cPNGHEad := chr(137)+chr(80)+chr(78)+chr(71)+chr(13)+chr(10)+chr(26)+chr(10)

IF !(Left(cBuffer,8) == cPNGHead)
   conout("Nao é um arquivo PNG")
   Return
Endif

// Corta o header
cBuffer := substr(cBuffer,9)

while len(cBuffer) > 0
	
	// Tamanho dos dados do chunk
	iTam := Bin4toN(left(cBuffer,4))
	cBuffer := substr(cBuffer,5)
	conout(replicate('-',79))
	conout("Chuck Size ... " + cValToChar(iTam))
	
	cType := left(cBuffer,4)
	cBuffer := substr(cBuffer,5)
	conout("Chuck Type ... " + cType)
	
	IF iTam > 0
		cData := left(cBuffer,iTam)
		cBuffer := substr(cBuffer,iTam+1)
		conout("Chuck Data ... ")
		conout(hexstrdump(cData))
	Else
		cData := ''
		conout("Empty Chuck")
	Endif
	
	cCRC := left(cBuffer,4)
	cBuffer := substr(cBuffer,5)
	nCRC := Bin4toN(cCRC)
	conout("Chuck CRC ... " + cValToChar(nCRC))
	conout("")
	
	If Upper(cType) == 'IHDR'
		
		conout("Header Chunck")
		
		/*
		
		Width:              4 bytes
		Height:             4 bytes
		Bit depth:          1 byte
		Color type:         1 byte
		Compression method: 1 byte
		Filter method:      1 byte
		Interlace method:   1 byte
		
		HexSTRDump  ( String 13 / Start 1 / Length 13 )
		-------------------------------------------------------------------------------
		00 00 04 62 00 00 00 78 01 03 00 00 00           | ___b___x_____
		-------------------------------------------------------------------------------
		*/
		
		nWidth := Bin4toN(substr(cData,1,4))
		nHeight := Bin4toN(substr(cData,5,4))
		nDepth := asc(substr(cData,9,1))
		nColorType := asc(substr(cData,10,1))
		nCompress := asc(substr(cData,11,1))
		nFilter := asc(substr(cData,12,1))
		nInterlace := asc(substr(cData,13,1))
		
		conout('Width ...... '+cValToChar(nWidth))
		conout('Height ..... '+cValToChar(nHeight))
		conout('Bit Depth .. '+cValToChar(nDepth))
		conout('Color Type . '+cValToChar(nColorType))
		conout('Compression. '+cValToChar(nCompress))
		conout('Filter ..... '+cValToChar(nFilter))
		conout('Interlace .. '+cValToChar(nInterlace))
		
	ElseIF Upper(cType) == 'SRGB'
		
		conout("SRGB Chunk") // 1 byte
		
		/*
		0: Perceptual
		1: Relative colorimetric
		2: Saturation
		3: Absolute colorimetric
		*/
		
		nRGBMode := asc(substr(cData,1,1))
		conout('RGB Mode .. '+cValToChar(nRGBMode))
		
	ElseIF Upper(cType) == 'GAMA'
		conout("GAMA Chunk")
		
		// The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
		// For example, a gamma of 1/2.2 would be stored as 45455.
		
		nGama := Bin4toN(substr(cData,1,4))
		conout('Gama Mode .. '+cValToChar(nGama))
		
	ElseIF Upper(cType) == 'PLTE'
		conout("PLTE Chunk")
		
		/*
		Paleta de cores monocromatica , sequencias de 3 bytes  RGB
		0,0,0 Black
		255,255,255 White
		HexSTRDump  ( String 6 / Start 1 / Length 6 )
		-------------------------------------------------------------------------------
		00 00 00 FF FF FF                                | ___   
		-------------------------------------------------------------------------------
		*/
		
		
	ElseIF Upper(cType) == 'PHYS'
		conout("PHYS Chunk")
		
		/*
		HexSTRDump  ( String 9 / Start 1 / Length 9 )
		-------------------------------------------------------------------------------
		00 00 12 74 00 00 12 74 01                       | ___t___t_
		-------------------------------------------------------------------------------
		
		Pixels per unit, X axis: 4 bytes (unsigned integer)
		Pixels per unit, Y axis: 4 bytes (unsigned integer)
		Unit specifier:          1 byte
		*/
		
		nPPUX := Bin4toN(substr(cData,1,4))
		nPPUY := Bin4toN(substr(cData,5,4))
		nPUnit := asc(substr(cData,9,1))
		
		conout('PixelPerUnit X .... '+cValToChar(nPPUX))
		conout('PixelPerUnit Y .... '+cValToChar(nPPUY))
		conout('Pixel Unit  ....... '+cValToChar(nPUnit))
		
		
	ElseIF Upper(cType) == 'IDAT'
		conout("IDAT Chunk")
		
		cBufferOut := ''
		nLenghtOut := 32*1024
		UnCompress( @cBufferOut ,  @nLenghtOut , cData, len(cData)  )
		
		// conout(hexstrdump(cBufferOut))
		
		nColSize := nLenghtOut / nHeight
		
		// CRia um bitmap monocromático
		oBMP := zBitmap():New(nWidth,nHeight,1)
		
		// Mastiga o buffer binário do PNG e cria o BITMAP
		For nL := 1 to 	nHeight
			cRowBuffer := substr(cBufferOut,1,nColSize)
			cBufferOut := substr(cBufferOut,nColSize+1)
			nColPos := 1
			For nC := 2 to nColSize
				cBits := NTOBIT8( asc(substr(cRowBuffer,nC,1)) )
				While len(cBits) > 0
					IF nColPos < nWidth
						oBMP:SETPIXEL(nL-1,nColPos-1,val(left(cBits,1)))
					Endif
					nColPos++
					cBits := substr(cBits,2)
				Enddo
			Next
		Next
		
		obmp:SavetoBMP('\meubitmap.bmp')
		
	ElseIF Upper(cType) == 'IEND'
		
		conout("IEND Chunk")
	Endif
	
	
enddo

Return

Objetivo

O Programa acima lê uma imagem de testes pequena — gerada originalmente pela zBarCode128() em formato BMP, convertida para PNG usando o PaintBrush do Windows. Como o objetivo do programa era entender o que tem dentro do PNG, ele têm vários comentários e vários conout() — echos de saída de console — para mostrar as informações que estão sendo identificadas. Pelo fato do PNG de testes ser pequeno, ele contém apenas um chunk de dados, que é descompactado em memória, e baseado nos dados binários retornados eu gero novamente em memória uma imagem BMP, e salvo ela novamente em disco com outro nome. Como este fragmento é apenas uma POC, ele não suporta (ainda) um PNG com mais de duas cores, nem um PNG com mais de um Chunk de dados.

Conclusão

Embora o suporte ao PNG envolva mais implementações, como múltiplos Chunks e múltiplas cores, uma vez que eu consegui fazer um programa que entenda um Chunk e o converta, isso torna possível escrever métodos adicionais na classe zBittmap para ler e gravar neste formato. Aguardem mais novidades nos próximos post 😀

E desejo novamente a todos TERABYTES DE SUCESSO !!!

Referências

  • PNG (Portable Network Graphics) Specification, Version 1.2