Bar Code 128 em AdvPL – Parte 01

Introdução

O famoso código de barras, presente em praticamente todos os itens industrializados à venda, também é usado para muitas outras finalidades, inclusive não apenas codificar números. Nesse post vamos ver como fazer um gerador de código de barras padrão Code 128 — que serve como base para outras especificações.

Code 128

Trata-se de uma especificação de código de barras de alta densidade, com sua simbologia definida pela ISO/IEC 15417:2007, que serve de base para outras implementações. É capaz de codificar praticamente toda a tabela ASCII, mas possui limitações intrínsecas ao seu tamanho, se comparado ao QR Code.

Simbologia

Cada “barrinha vertical” é chamado de módulo. O código de barras é formado por sequências de módulos preenchidos, separados por módulos em branco, onde podemos ter de um a quatro módulos preenchidos ou vazios em sequência. A codificação em barras é praticamente uma representação binária ( 0 = módulo vazio, 1 = módulo preenchido)

Para a sequência binária fazer sentido e gerar uma informação distinguível, ela e composta por um alfabeto binário de 11 bits, com 108 símbolos, sendo 103 de dados e 5 símbolos de controle. Três códigos de controle são usados para permitir trocar a interpretação da sequência posterior de símbolos, permitindo representar muita coisa 😀

Formato

Basicamente o código de barras Code 128 é composto por uma “zona neutra” de 10 módulos em branco (margem esquerda e direita), seguida por um símbolo de “Start”, que indica a interpretação dos símbolos de dados, seguida por símbolos de dados, um checksum, e o símbolo de parada (Stop), seguido de mais dois módulos preenchidos — visto como “Stop Pattern”, que têm 13 bits.

Codificação

Como existem 3 tabelas de interpretação de valores para os símbolos de dados (A, B e C), você deve avaliar o conteúdo a ser codificado para usar o alfabeto que procure gerar a menor sequência de símbolos possível. Para representação de apenas números, o alfabeto C é perfeito, pois cada símbolo de dados representa um número de dois dígitos ( de 00 a 99 ), e em caso de representação de número ímpar de dígitos, podemos trocar o alfabeto no meio dos dados e usar um símbolo da tabela A.

Caso a representação seja alfanumérica, normalmente usamos a tabela B, podendo optimizar a representação de números caso existam sequencias numéricas superiores a 4 dígitos em sequência. A documentação da Wikipedia — vide referências no final do post — explica bem o processo. Basicamente, você escolhe inicialmente o alfabeto a usar, depois procura qual o valor numérico correspondente da letra ou número ou dupla de números a utilizar dentro do alfabeto em uso, e guarda todos os valores em um array numérico.

CheckSum

Tendo em mãos o array com a sequência de dados, o cálculo do CheckSum é o módulo 103 da soma do valor de cada elemento do array multiplicado por um fator de peso, sequencial e incremental, onde os dois primeiros elementos do array têm peso 1, e os demais o peso incrementado em uma unidade.

Fonte de uso das classes de emissão

São usadas duas classes da zLib para a emissão do código de barras Code 128. a classe zCodeBar128() e a classe zBitmap(). Praticamente são meia dúzia de linhas.

#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 
oBmp:SaveToBMP('\barcode.bmp')

Return

É claro que existem muitas coisas pré-definidas neste contexto. Por exemplo, a imagem  Bitmap gerada é monocromática, e cada módulo tem a espessura de um pixel. O método Generate() gera a sequência de símbolos que compõe o código de barras completo — start, data, checksum e stop — e o método BuildBMP() cria um bitmap usando esta sequência. Agora vamos ver por dentro a classe zBarCode128:

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

/* ======================================================
Classe zBarCode128
Autor Julio Wittwer
Data 19/07/2019
Descrição Permite gerar um Codigo de Barras Code 128
====================================================== */

Class zBarCode128 FROM LONGNAMECLASS

  METHOD NEW()
  METHOD Generate() 
  METHOD BuildBmp(aSeq)

Endclass

METHOD NEW() CLASS zBarCode128
Return self

METHOD Generate(cBarData) CLASS zBarCode128
Local lOnlyNum
Local nTam
Local cBarData
Local nCheck
Local aSequence := {}

If empty(cBarData)
  Return NIL
Endif

nTam := len(cBarData)
lOnlyNum := IsNumbers(cBarData)

If lOnlyNum

  // Geração numérica
  // pode alternar Code A e Code C para zeros
  // Code C trabalha sempre em blocos de 2 numeros
  // Boa pratica : Com numero de digitos impar, coloca o primeiro
  // digito com Code A , e as demaps tuplas em Code C
  IF (nTam % 2) != 0
    // Impar, comeca com CODE A
    aadd(aSequence , 103 )
    aadd(aSequence , val(left(cBarData,1))+16 )
    cBarData := substr(cBarData,2)
  Else
    // Par, codifica direto em Code C
    aadd(aSequence , 105 )
  Endif

  // Codifica os numeros em pares
  While len(cBarData) > 0 
    nTupla := val(left(cBarData,2))
    aadd(aSequence , nTupla )
    cBarData := substr(cBarData,3)
  Enddo

Else

  // Alfanumérico
  // por enquanto chumbado direto com Code B ( alfabeto maior ) 
  aadd(aSequence , 104 )

  While len(cBarData) > 0 
    cChar := left(cBarData,1)
    nValor := GetCodeB(cChar)
    aadd(aSequence , nValor )
    cBarData := substr(cBarData,2)
    Enddo

Endif

// Com a sequencia montada, agora gera o checksum 
nCheck := CheckSum(aSequence)

// Acrescena o Checksum 
aadd(aSequence,nCheck)

// Finaliza com o STOP Pattern
aadd(aSequence,108)

Return aSequence

// -- Montagem do BITMAP 
METHOD BuildBmp(aSeq) CLASS zBarCode128
Local nI
Local cPlot := ''
Local oBmp

// Monta a sequência binária das barras 
for nI := 1 to len(aSeq)
  // conout(str(aSeq[nI],3)+' | '+GetBitBar(aSeq[nI]))
  cPlot += GetBitBar(aSeq[nI])
Next

// Cria o Bitmap Monocromatico na memória 
oBMP := ZBITMAP():NEW( len(cPlot)+20,40, 1 )
oBMP:nPenSize := 1

// Pinta as barras
For nI := 1 to len(cPlot)
  If substr(cPlot,nI,1) == '1'
    oBmp:Line(5,nI+9,35,nI+9,0,1)
  Endif
Next

Return oBmp

// Função auxiliar, identifica se o texto contém apenas números
STATIC Function IsNumbers(cData)
Local nI , nT
Local cChar
nT := len(cData)
For nI := 1 to nT
  cChar := substr(cData,nI,1)
  If cChar < '0' .or. cChar > '9'
    Return .F.
  Endif
Next
Return .T.

// Carga do alfabeto de símbolos e sequencias
STATIC _aBar128 := LoadBar128()

STATIC Function LoadBar128()
Local aBar128 := {}

// Value,128A,128B,128C,Pattern,Widths

aadd( aBar128 , { 0,' ',' ','00','11011001100','212222'})
aadd( aBar128 , { 1,'!','!','01','11001101100','222122'})
aadd( aBar128 , { 2,'"','"','02','11001100110','222221'})
aadd( aBar128 , { 3,'#','#','03','10010011000','121223'})
aadd( aBar128 , { 4,'$','$','04','10010001100','121322'})
aadd( aBar128 , { 5,'%','%','05','10001001100','131222'})
aadd( aBar128 , { 6,'&','&','06','10011001000','122213'})
aadd( aBar128 , { 7,"'","'",'07','10011000100','122312'})
aadd( aBar128 , { 8,'(','(','08','10001100100','132212'})
aadd( aBar128 , { 9,')',')','09','11001001000','221213'})
aadd( aBar128 , { 10,'*','*','10','11001000100','221312'})
aadd( aBar128 , { 11,'+','+','11','11000100100','231212'})
aadd( aBar128 , { 12,',',',','12','10110011100','112232'})
aadd( aBar128 , { 13,'-','-','13','10011011100','122132'})
aadd( aBar128 , { 14,'.','.','14','10011001110','122231'})
aadd( aBar128 , { 15,'/','/','15','10111001100','113222'})
aadd( aBar128 , { 16,'0','0','16','10011101100','123122'})
aadd( aBar128 , { 17,'1','1','17','10011100110','123221'})
aadd( aBar128 , { 18,'2','2','18','11001110010','223211'})
aadd( aBar128 , { 19,'3','3','19','11001011100','221132'})
aadd( aBar128 , { 20,'4','4','20','11001001110','221231'})
aadd( aBar128 , { 21,'5','5','21','11011100100','213212'})
aadd( aBar128 , { 22,'6','6','22','11001110100','223112'})
aadd( aBar128 , { 23,'7','7','23','11101101110','312131'})
aadd( aBar128 , { 24,'8','8','24','11101001100','311222'})
aadd( aBar128 , { 25,'9','9','25','11100101100','321122'})
aadd( aBar128 , { 26,':',':','26','11100100110','321221'})
aadd( aBar128 , { 27,';',';','27','11101100100','312212'})
aadd( aBar128 , { 28,'<','<','28','11100110100','322112'})
aadd( aBar128 , { 29,'=','=','29','11100110010','322211'})
aadd( aBar128 , { 30,'>','>','30','11011011000','212123'})
aadd( aBar128 , { 31,'?','?','31','11011000110','212321'})
aadd( aBar128 , { 32,'@','@','32','11000110110','232121'})
aadd( aBar128 , { 33,'A','A','33','10100011000','111323'})
aadd( aBar128 , { 34,'B','B','34','10001011000','131123'})
aadd( aBar128 , { 35,'C','C','35','10001000110','131321'})
aadd( aBar128 , { 36,'D','D','36','10110001000','112313'})
aadd( aBar128 , { 37,'E','E','37','10001101000','132113'})
aadd( aBar128 , { 38,'F','F','38','10001100010','132311'})
aadd( aBar128 , { 39,'G','G','39','11010001000','211313'})
aadd( aBar128 , { 40,'H','H','40','11000101000','231113'})
aadd( aBar128 , { 41,'I','I','41','11000100010','231311'})
aadd( aBar128 , { 42,'J','J','42','10110111000','112133'})
aadd( aBar128 , { 43,'K','K','43','10110001110','112331'})
aadd( aBar128 , { 44,'L','L','44','10001101110','132131'})
aadd( aBar128 , { 45,'M','M','45','10111011000','113123'})
aadd( aBar128 , { 46,'N','N','46','10111000110','113321'})
aadd( aBar128 , { 47,'O','O','47','10001110110','133121'})
aadd( aBar128 , { 48,'P','P','48','11101110110','313121'})
aadd( aBar128 , { 49,'Q','Q','49','11010001110','211331'})
aadd( aBar128 , { 50,'R','R','50','11000101110','231131'})
aadd( aBar128 , { 51,'S','S','51','11011101000','213113'})
aadd( aBar128 , { 52,'T','T','52','11011100010','213311'})
aadd( aBar128 , { 53,'U','U','53','11011101110','213131'})
aadd( aBar128 , { 54,'V','V','54','11101011000','311123'})
aadd( aBar128 , { 55,'W','W','55','11101000110','311321'})
aadd( aBar128 , { 56,'X','X','56','11100010110','331121'})
aadd( aBar128 , { 57,'Y','Y','57','11101101000','312113'})
aadd( aBar128 , { 58,'Z','Z','58','11101100010','312311'})
aadd( aBar128 , { 59,'[','[','59','11100011010','332111'})
aadd( aBar128 , { 60,'\','\','60','11101111010','314111'})
aadd( aBar128 , { 61,']',']','61','11001000010','221411'})
aadd( aBar128 , { 62,'^','^','62','11110001010','431111'})
aadd( aBar128 , { 63,'_','_','63','10100110000','111224'})
aadd( aBar128 , { 64,chr(0),'`','64','10100001100','111422'})
aadd( aBar128 , { 65,chr(1),'a','65','10010110000','121124'})
aadd( aBar128 , { 66,chr(2),'b','66','10010000110','121421'})
aadd( aBar128 , { 67,chr(3),'c','67','10000101100','141122'})
aadd( aBar128 , { 68,chr(4),'d','68','10000100110','141221'})
aadd( aBar128 , { 69,chr(5),'e','69','10110010000','112214'})
aadd( aBar128 , { 70,chr(6),'f','70','10110000100','112412'})
aadd( aBar128 , { 71,chr(7),'g','71','10011010000','122114'})
aadd( aBar128 , { 72,chr(8),'h','72','10011000010','122411'})
aadd( aBar128 , { 73,chr(9),'i','73','10000110100','142112'})
aadd( aBar128 , { 74,chr(10),'j','74','10000110010','142211'})
aadd( aBar128 , { 75,chr(11),'k','75','11000010010','241211'})
aadd( aBar128 , { 76,chr(12),'l','76','11001010000','221114'})
aadd( aBar128 , { 77,chr(13),'m','77','11110111010','413111'})
aadd( aBar128 , { 78,chr(14),'n','78','11000010100','241112'})
aadd( aBar128 , { 79,chr(15),'o','79','10001111010','134111'})
aadd( aBar128 , { 80,chr(16),'p','80','10100111100','111242'})
aadd( aBar128 , { 81,chr(17),'q','81','10010111100','121142'})
aadd( aBar128 , { 82,chr(18),'r','82','10010011110','121241'})
aadd( aBar128 , { 83,chr(19),'s','83','10111100100','114212'})
aadd( aBar128 , { 84,chr(20),'t','84','10011110100','124112'})
aadd( aBar128 , { 85,chr(21),'u','85','10011110010','124211'})
aadd( aBar128 , { 86,chr(22),'v','86','11110100100','411212'})
aadd( aBar128 , { 87,chr(23),'w','87','11110010100','421112'})
aadd( aBar128 , { 88,chr(24),'x','88','11110010010','421211'})
aadd( aBar128 , { 89,chr(25),'y','89','11011011110','212141'})
aadd( aBar128 , { 90,chr(26),'z','90','11011110110','214121'})
aadd( aBar128 , { 91,chr(27),'{','91','11110110110','412121'})
aadd( aBar128 , { 92,chr(28),'|','92','10101111000','111143'})
aadd( aBar128 , { 93,chr(29),'}','93','10100011110','111341'})
aadd( aBar128 , { 94,chr(30),'~','94','10001011110','131141'})
aadd( aBar128 , { 95,chr(31),NIL,'95','10111101000','114113'}) // DEL
aadd( aBar128 , { 96, NIL ,NIL,'96','10111100010','114311'}) // FNC 3','FNC 3'
aadd( aBar128 , { 97, NIL ,NIL,'97','11110101000','411113'}) // 'FNC 2','FNC 2'
aadd( aBar128 , { 98, NIL ,NIL,'98','11110100010','411311'}) // 'Shift B','Shift A'
aadd( aBar128 , { 99, NIL ,NIL,'99','10111011110','113141'}) // 'Code C','Code C'
aadd( aBar128 , {100, NIL ,NIL, NIL,'10111101110','114131'}) // 'Code B','FNC 4','Code B'
aadd( aBar128 , {101, NIL ,NIL, NIL,'11101011110','311141'}) // 'FNC 4','Code A','Code A'
aadd( aBar128 , {102, NIL ,NIL, NIL,'11110101110','411131'}) // 'FNC 1','FNC 1','FNC 1'
aadd( aBar128 , {103, NIL ,NIL, NIL,'11010000100','211412'}) // 3x'Start Code A'
aadd( aBar128 , {104, NIL ,NIL, NIL,'11010010000','211214'}) // 3x'Start Code B'
aadd( aBar128 , {105, NIL ,NIL, NIL,'11010011100','211232'}) // 3x'Start Code C'
aadd( aBar128 , {106, NIL ,NIL, NIL,'11000111010','233111'}) // 'Stop','—','—'
aadd( aBar128 , {NIL, NIL ,NIL, NIL,'11010111000','211133'}) // Reverse Stop
aadd( aBar128 , {NIL, NIL ,NIL, NIL,'1100011101011','2331112'}) // Stop pattern (7 bars/spaces)

Return aBar128

// Função auxiliar -- Calcula o CheckSum
STATIC Function CheckSum(aSequence)
Local nI , nT , nSum := 0 , nCheck
nT := len(aSequence)
nSum := aSequence[1]
For nI := 2 to nT 
  nSum += ( aSequence[nI] * (nI-1) )
Next
nCheck := nSum % 103 
Return nCheck

// Função auxiliar, retorna a string binária do símbolo pelo número
STATIC Function GetBitBar(nValue)
Return _aBar128[nValue+1][5]

// Função auxiliar, busca no alfabeto B o número do símbolo correspondente
STATIC Function GetCodeB(cChar)
Local nPos := ascan(_aBar128,{|x| x[3] == cChar })
Return _aBar128[nPos][1]

Exemplo

Veja abaixo como ficou o código de barras representando a URL de acesso ao blog ( https://siga0984.wordpress.com)

barcode

Conclusão

A orientação a objetos definitivamente facilita o reaproveitamento de código, e permite criar componentes de mais alto nível de forma simples, e nada que alguns parâmetros ou métodos a mais permitam especializar ou herdar dessa base para implementar especificações subsequentes.

Espero que tenham gostado, e desejo a todos TERABYTES de sucesso !!! 

Referências

 

QR Code em AdvPL – Parte 03

Introdução

No post anterior vimos uma breve introdução a classe ZQRCODE. Agora, vamos ver um fonte usando essa classe ! A classe faz parte do projeto ZLIB, fontes disponíveis no GITHUB.

QR Code Print

Geração de QR Code

A geração da imagem de um determinado QR Code envolve duas etapas. A primeira é criar a matriz de bits ou Grid do QR Code, e a segunda é a criação da imagem — por hora um Bitmap monocromático. O fonte abaixo dá conta do recado usando a classe ZQRCODE e a ZBITMAP.

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

#define MODULE_PIXEL_SIZE     12   //  Tamanho em PIXELs do Modulo

/* ------------------------------------------------
Primeira interface para a criação de um QR Code 
------------------------------------------------ */

USER Function NewQRCode()
Local oDlg
Local cQRData := space(240)
Local cErrC , aErrorC := {}
Local nMode := 2  // Alfanumerico
Local aMasks := {}                
Local cMask 
Local nI
Local oQR

// Cria uma caixa de diálogo com área util de 800x600  PIXELs
DEFINE DIALOG oDlg TITLE "QR Code em AdvPL" FROM 0,0 TO 600,800 PIXEL

// Monta um GET para entrada de dados em variável string
// @ 18,15 SAY oSay1 PROMPT "Get String" SIZE 30,12 OF oDlg  PIXEL
@ 18,15 SAY "QR Data" PIXEL
@ 15,50 GET oGet1 VAR cQRData MULTILINE SIZE 260,36 OF oDlg  PIXEL

// Lista de níveis de correções de erro disponíveis 
aadd(aErrorC,"L.Low")
aadd(aErrorC,"M.Medium")
aadd(aErrorC,"Q.Quartile")
aadd(aErrorC,"H.High")

// Monta um objeto Combo, para escolha de string entre elementos de um array
@ 58,15 SAY "Error C" PIXEL
@ 55,50 COMBOBOX oComboBox1 VAR cErrC ITEMS aErrorC SIZE 80,12 OF oDlg  PIXEL

// Lista de Tipos de Mascaramento de dados 
// TODO - Escolha automatica de mascaramento 
// aadd(aMasks,"A.Auto")
For nI := 0 to 7
	aadd(aMasks,cValToChar(nI)+".Mask "+cValToChar(nI))
Next

// Monta um objeto Combo, para escolher o mascaramento de dados
// ou deixar no automatico ( TODO ) 
@ 73,15 SAY "Mask" PIXEL
@ 70,50 COMBOBOX oCombo2 VAR cMask ITEMS aMasks SIZE 80,12 OF oDlg  PIXEL

// Area reservada a Imagem 
@ 55,135 BITMAP oBmpQRCode OF oDlg PIXEL 
  
// Reserva a imagem em branco com 
// o Tamanho da versao 1 com margem de 4 modulos ) 
oBmpQRCode:nWidth := 29*MODULE_PIXEL_SIZE  
oBmpQRCode:nHeight := 29*MODULE_PIXEL_SIZE
oBmpQRCode:lStretch := .T.

// Botao para a geracão do QR Code
@ 85,50 BUTTON oButton1 PROMPT "&Gerar QR Code" ;
  ACTION (NewQRCode(@oQR,cQRData,cErrC,nMode,cMask,oBMPQRCode)) ;
  SIZE 080, 013 of oDlg  PIXEL

@ 100,50 BUTTON oButton2 PROMPT "&Exportar Imagem" ;
  ACTION (ExportImg(oQR)) WHEN oQR != NIL ;
  SIZE 080, 013 of oDlg  PIXEL

ACTIVATE DIALOG oDlg CENTER 

Return

// ------------------------------------------------
// Geração do QR Code na interface

STATIC Function NewQRCode(oQR,cQRData,cErrC,nMode,cMask,oBMPQRCode)

IF oQR != NIL
	FreeObj(oQR)
Endif

// Cria QRCode vazio 
oQR := ZQRCODE():New()

// Trata os dados informados 
cQRData := alltrim(cQRData)
cErrC := left(cErrC,1)
cMask := left(cMask,1)
                             
If empty(cQRData)
	MsgStop("Dados para o QR Code não informados.")
Endif

// Inicializa o QRCode com Error Correction "L", sem versao definida
// Informa o Error Correction
oQR:SetEC(cErrC)

// Seta o dado a ser colocado no QRCode
// e seta o modo de codificacao ( 1 = Numerico, 2 = Alfanumerico, 3 = Bytes , 4 = Kanji )
oQR:SetData(cQRData,nMode)

// Monta a matriz de dados 
// Se a versao nao foi especificada, determina de acordo 
// com o tamanho dos dados a serem plotados
// oQR:oLogger:SETECHO(.T.)

oQR:BuildData()

// oQR:oLogger:SETECHO(.F.)

// Monta a matriz final do QRCode 
oQR:DeployBits()

/*
conout("After Deploy Bits ")
oQR:PrintBits()
*/

// Seleciona a mascara de dados 
// Caso seja automatico, nao informa
// para a engine de emissao decidir
// O método SelectMask já aplica a máscara
If cMask <> 'A'
	oQR:SelectMask(val(cMask))
Else
	oQR:SelectMask()
Endif

/*
conout("After Mask Data ")
oQR:PrintBits()
*/

// Exporta para um BITMAP monocromatico
// Margem minima = 4 modulos ( de cada lado ) 
oBMP := ZBITMAP():NEW(oQR:nSize+8,oQR:nSize+8,1)

// Plota os módulos setados com 1 
// com a cor preta no Bitmap

For nL := 1 to oQR:nSize
	For nC := 1 to oQR:nSize
		If oQR:aGrid[nL][nC] == 1 .or. oQR:aGrid[nL][nC] == 3
			oBmp:SetPixel(nL+3,nC+3,0)
		Endif
	NExt
Next	

// Gera a imagem em disco 
// CRia um arquivo temporario com nome unico 
// para driblar o cache de imagem da interface
cFile := '\qrtmp_'+cvaltochar(seconds())+'.bmp'
oBmp:SaveToBmp(cFile)

// Redimensiona a imagem na tela
// com o tamanho da versao escolhida +margens
oBmpQRCode:nWidth := (oQR:nSize+8)*MODULE_PIXEL_SIZE 
oBmpQRCode:nHeight := (oQR:nSize+8)*MODULE_PIXEL_SIZE
oBmpQRCode:lStretch := .T.

// Carrega a imagem gerada na interface
oBMPQRCode:Load(,cFile)

// apaga a imagem temporária do disco 
// e limpa a classe bitmap da memoria 
ferase(cFile)
FreeObj(oBmp)

Return


// ------------------------------------------
// Exporta o QR Code para imagem BITMAP 

STATIC Function ExportImg(oQR)
Local nImgSize
Local oBmp
Local nL
Local nC
Local nL1,nL2,nC1,nC2
Local cFile

nImgSize := ( oQR:nSize+8 ) * MODULE_PIXEL_SIZE

oBMP := ZBITMAP():NEW( nImgSize,nImgSize, 1 )
oBMP:nPenSize := 1

// Plota os módulos setados com 1 
// com quadrados vazados no Bitmap
// e pinta o interior dos quadrados

For nL := 1 to oQR:nSize
	For nC := 1 to oQR:nSize
		If oQR:aGrid[nL][nC] == 1 .or. oQR:aGrid[nL][nC] == 3
		    nL1 := (nL+2)*MODULE_PIXEL_SIZE
		    nC1 := (nC+2)*MODULE_PIXEL_SIZE
		    nL2 := nL1+MODULE_PIXEL_SIZE
		    nC2 := nC1+MODULE_PIXEL_SIZE
			oBmp:Rectangle(nL1,nC1,nL2,nC2,0)
			oBmp:Paint(nL1+1,nC1+1)
		Endif
	Next
Next	

// Salva o arquivo em disc 
cFile := '\QRCode.bmp'
oBmp:SaveToBmp(cFile)
FreeObj(oBmp)

MsgInfo("Imagem do QR Code exportada para o arquivo ["+cFile+"]")

Return

Por hora a aplicação gera um QR Code com capacidade aproximada de até 200 caracteres, usando uma codificação Alfanumérica restrita de 44 caracteres. É suficiente para textos simples e URLs. O fonte da implementação será destrinchado em próximos posts da sequência, afinal ele está com quase 3000 linhas de código — e ainda não está completo.

Exportação da Imagem

A imagem exportada é criada como um BITMAP monocromático, com cada módulo desenhado com um quadrado de 12 pixels. Podemos diminuir no fonte este tamanho, tomando apenas o cuidado de gerar um QR Code com tamanho suficiente para o scanner ou aplicativo usado para leitura seja capaz de lê-lo. No objeto BITMAP criado é possível aumentar o número de cores da imagem, trocar a cor de plotagem, etc.

Mascaramento de Dados

Existem oito formas de mascaramento de dados, para gerar um QR Code que seja mais fácil ou menos suscetível de erro de leitura pelo Scanner. Atualmente a implementação ainda requer que você informe qual o mascaramento desejado, a parte que faz a avaliação dos mascaramentos para escolha automática ainda está em desenvolvimento.

Próximas etapas

O objetivo da implementação é suportar até o nível 40 com correção de erro “H” (High), mas para isso ainda é necessário codificar a separação dos dados em grupos para a geração do Grid do QR Code — também em desenvolvimento.

Conclusão

Não sabendo que era impossível, ele foi lá e fez. — Jean Cocteau

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

 

 

Bitmaps em AdvPL – Parte 08

Introdução

No post anterior, vimos com mais detalhes o suporte a corres de uma imagem Bitmap. Agora, vamos ver mais um método interessante da classe ZBITMAP — o FlipV() — ou inversão vertical — usado para colocar a imagem ou uma parte dela literalmente “de ponta cabeça”. Quer ver esta sequência desde o início? Acesse clicando aqui o primeiro post.

Método FlipV()

Este método pode receber opcionalmente duas coordenadas, para permitir inverter verticalmente uma área da imagem. Caso nenhum parâmetro seja informado, ele inverte a imagem inteira. Ele é praticamente uma implementação “prima” da FlipH(), porém atua trocando as cores entre as linhas da área informada, enquanto a FlipH() troca as cores das colunas.

Como os dados do Bitmap estão em um array bidimensional, existem várias formas de se realizar a inversão. Em ambas as implementações, eu optei por usar a alternativa mais “econômica” de memória, criando uma instrução na forma de #translate para fazer uma troca de conteúdo entre duas variáveis, no caso a cor do ponto no array, sem alocar arrays intermediários ou temporários. Apenas tomei o cuidado de declarar uma variável local para ser usada como container temporário para fazer a troca sem perder a informação original. Vamos ao método:

// Inverte verticalmente uma área da imagem
// Ou a imagem inteira caso a área nao seja especificada
METHOD FlipV(L1,C1,L2,C2) CLASS ZBITMAP
Local nL  , nC            
Local nRow, nSwap
IF pCount() == 0
	// Faz flip vertical da imagem inteira
	L1 := 0
	C1 := 0
	L2 := ::nHeight-1
	C2 := ::nWidth-1
Else
	// Valida coordenadas informados
	IF L1 < 0 .or. L1 >= ::nHeight
		::cError := "Invalid 1o Line -- Out Of Image Area"
		Return .F.
	ElseIF L2 < 0 .or. L2 >= ::nHeight
		::cError := "Invalid 2o Line -- Out Of Image Area"
		Return .F.
	ElseIf C1 < 0 .or. C1 >= ::nWidth
		::cError := "Invalid 1o Column -- Out Of Image Area"
		Return .F.
	ElseIf C2 < 0 .or. C2 >= ::nWidth
		::cError := "Invalid 2o Column -- Out Of Image Area"
		Return .F.
	ElseIf L1 > L2
		::cError := "Invalid Lines -- Order mismatch"
		Return .F.
	ElseIf C1 > C2
		::cError := "Invalid Columns -- Order mismatch"
		Return .F.
	Endif
Endif

// Troca os pontos da primeira linha com a ultima
// depois da segunda com a penultima 
// até chegar na linha central da área a ser invertida      
nRow := L2+1
For nL := L1+1 to L1 + INT( ( L2-L1 ) / 2 ) + 1
	For nC := C1+1 to C2+1
		ZSWAP( ::aMatrix[nL][nC] , ::aMatrix[nRow][nC] , nSwap )
	Next
	nRow--
Next

Return .T.

Instrução ZSWAP

Se eu quero trocar conteúdos entre duas variáveis, para que uma assuma o conteúdo da outra, (ainda) não existe função da linguagem que permita fazer isso de forma direta. A alternativa mais eficiente é armazenar o conteúdo da primeira variável em uma variável de uso temporário, depois atribuir o conteúdo da segunda variável sobre a primeira, e então atribuir o conteúdo da primeira variável — salvo e armazenado na variável temporária — na segunda variável. Para isso, eu uso a instrução abaixo:

#TRANSLATE ZSWAP( <X> , <Y> , <S> ) => ( <S> := <X> , <X> := <Y> , <Y> := <S> )

Usando a diretiva de pré-compilação #TRANSLATE, é possível criar uma pseudo-função em tempo de compilação — algo equivalente a um método “inline”, disponível por exemplo no C++. A diretiva identifica as ocorrências de chamada da pseudo-função ZSWAP, recebendo três parâmetros: <X> é a primeira variável ou expressão, <Y> é a segunda variável ou expressão, e <S> é a variável de armazenamento temporário para fazer a troca. Veja abaixo um exemplo de uso e como ela será traduzida na pré-copilação deste código:

// Chamada original
ZSWAP ( nVar1 , nVar2, nTmp ) 

// Após a pré-compilação 
( nTmp := nVar1 , nVar1 := nVar2 , nVar2 := nTmp )

A utilização de diretivas de #TRANSLATE e similares no AdvPL (como a #COMMAND e #XCOMMAND) permite implementações desta ordem, e também aglutinação e tradução de chamadas, até a implementação de comandos — que serão traduzidos para chamadas de funções ou métodos, tornando a sintaxe para diversas operações bem mais leve e intuitiva.

Conclusão

Por hora, concluo apenas que falta pouco para a classe ZBITMAP ser declarada como “pronta”.

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

 

Bitmaps em AdvPL – Parte 07

Introdução

No post anterior, vimos os métodos Cut(), Copy(), Paste() e FlipH(). Agora, vamos ver o suporte a cores, desde o monocromático até 24 bits (ou True Color).

Mapa de Cores

Nas versões de cores implementadas até agora — monocromático, 16 cores e 256 cores — exite uma informação entre o Header do Bitmap e a área de dados, onde é armazenada a tabela de cores em uso. São 4 bytes para cada cor armazenada na tabela, no  formato BGRA ( Blue, Green, Red e Alpha). No Bitmap monocromático, são apenas duas cores ( 0,0,0,0 e 255,255,255,0 ou Preto e Branco), tando que a representação de pontos da imagem no Bitmap Array é feita por BITS — cada Byte representa o estado de 8 pixels ( 0=preto, 1=branco).

No Bitmap de 16 cores, existem 16 cores na tabela padrão após o Header, que podem ser trocadas no mapa. Cada byte da área de dados usa 4 bits (ou um nibble) para representar a posição do mapa de cores para aquele ponto. Por isso o Bitmap de 16 cores utiliza 4 bits por pixel (4 BPP).

No Bitmap de 256 cores, como é de se esperar, o mapa de cores têm 256 cores pré-definidas, e cada pixel no mapa de dados da imagem é representado por um byte inteiro, contendo o valor da cor no índice, de 0 a 255.

Já no Bitmap de 24 bits (também conhecido por True Color), não há mapa de cores. Cada pixel no mapa de dados da imagem é representado por uma sequência de 4 bytes, n formato BGRA, que corresponde a uma cor distinta. Como cada uma das três cores possui intensidades de 0 a 255, isso permite representar mais cores do que o olho humano é capaz de distinguir — total de 16.777.216 cores. Naturalmente um Bitmap com esta definição de cores ocupa aproximadamente 4 vezes mais de tamanho do que o mesmo Bitmap em 256 cores.

Representação da cor em cada ponto

A propriedade ::aMatrix da classe ZBITMAP contém apenas o número de uma cor, quando usado qualquer um dos formatos inferior a 24 bits. Quando usamos esta capacidade de cores (True Color), ainda assim cada ponto apenas possui um número que representa aquela cor. Ele é formado pela conversão para decimal da representação binária das cores Vermelha, Verde e Azul (RGB) em sequência. Por exemplo, existe um tom de vermelho chamado “Tomate”, sua representação em RGB é 255, 99, 71 (decimal).

RGB ( 255 , 99 , 71 ) em 24 bits, nesta ordem, é :
255 = 11111111
 99 = 01100011
 71 = 01000111

Representação final : "111111110110001101000111"
Convertendo esta sequencia para decimal, temos o número 16737095

Logo, um ponto com a cor 16737095 corresponde ao tom de vermelho “tomate”. Quando o Bitmap de 24 bits é carregado, as cores são convertidas nesta representação numérica decimal. No momento de gravar a imagem após uma alteração, os números são reconvertidos para a sequência binária de 24 bits, e cada grupo de 8 bits gera um byte com o valor da respectiva cor.

Mudança no método NEGATIVE

O efeito de foto negativa, anteriormente feito alterando a tabela de cores, deve ser realizado ponto a ponto no Bitmap de 24 bits, aplicando a mesma regra do complemento para as cores RGB — 255 menos a cor. O método fica assim:

METHOD Negative()  CLASS ZBITMAP
Local nI
Local nL , nC
Local nRed := 0
Local nGreen := 0
Local nBlue := 0

If ::nBPP < 24
	// Formatos menores que True Color, altera a tabela de cores
	For nI := 1 to len(::aColors)
		::aColors[nI][1] := 255-::aColors[nI][1]
		::aColors[nI][2] := 255-::aColors[nI][2]
		::aColors[nI][3] := 255-::aColors[nI][3]
	Next
Else
	// Imagem True Color, inverte as cores ponto a ponto                      
	For nL := 1 to ::nHeight
		For nC := 1 to ::nWidth
			RGB2Dec(::aMatrix[nL][nC],@nRed,@nGreen,@nBlue)
			nRed := 255 - nRed
			nGreen := 255 - nGreen
			nBlue := 255 - nBlue
			::aMatrix[nL][nC] := RGB(nRed,nGreen,nBlue)
		Next
	Next
Endif
Return

Em memória, as cores são armazenadas e calculadas em RGB, apenas na leitura e gravação a ordem é invertida para o formato de armazenamento (BGR).

Conclusão

Fonte atualizado no GITHUB, divirta-se 😀

E desejo a todos TERABYTES DE SUCESSO !!!

Referências

 

Bitmaps em AdvPL – Parte 06

Introdução

No post anterior vimos como traçar um quadrado ou retângulo no Bitmap, e como fazer o efeito “negativo”. Agora, vamos implementar uma área de transferência interna na classe ZBITMAP, para fazer CUT() , COPY() e PASTE(), e fazer inversão horizontal da imagem ou de uma parte dela, usando o método FLIPH()

Operações e área de transferência

Como representamos a imagem em um array (::aMatrix), implementamos a área de transferência também como um Array bidimensional de cores. O método Clear(), que pintava todos os pontos da imagem com a cor de fundo, passa a receber opcionalmente parâmetros de duas coordenadas para limpar uma área retangular, limpando a imagem inteira apenas quando nenhuma coordenada for informada. O método COPY() cria um array com a área retangular das coordenadas informadas, o método CUT() usa internamente o COPY() e o CLEAR(), e o método PASTE() recebe a coordenada superior esquerda onde a imagem na área de transferência deve ser “colada”. Vejamos abaixo os métodos:

// ----------------------------------------------------
// Copia uma parte da imagem para a área interna 
// de transferência e limpa a área da imagem

METHOD Cut(L1,C1,L2,C2)            CLASS ZBITMAP
::Copy(L1,C1,L2,C2)
::Clear(L1,C1,L2,C2)
Return .T. 

// ----------------------------------------------------
// Copia uma parte da imagem para a área interna de transferência

METHOD Copy(L1,C1,L2,C2)           CLASS ZBITMAP
Local nL  , nC            
Local aRow := {}

IF pCount() == 0
	// Copia a imagem inteira para a area de transferencia
	::aTrans := aClone(::aMatrix)
    Return .T.
Endif

// Valida coordenadas informados
IF L1 < 0 .or. L1 >= ::nHeight
	::cError := "Invalid 1o Line -- Out Of Image Area"
	Return .F.
ElseIF L2 < 0 .or. L2 >= ::nHeight
	::cError := "Invalid 2o Line -- Out Of Image Area"
	Return .F.
ElseIf C1 < 0 .or. C1 >= ::nWidth
	::cError := "Invalid 1o Column -- Out Of Image Area"
	Return .F.
ElseIf C2 < 0 .or. C2 >= ::nWidth
	::cError := "Invalid 2o Column -- Out Of Image Area"
	Return .F.
ElseIf L1 > L2
	::cError := "Invalid Lines -- Order mismatch"
	Return .F.
ElseIf C1 > C2
	::cError := "Invalid Columns -- Order mismatch"
	Return .F.
Endif

::aTrans := {}

// Copia a área informada para a area de transferencia interna
For nL := L1+1 to L2+1
	For nC := C1+1 to C2+1
		aadd(aRow,::aMatrix[nL][nC])
	Next
	aadd(::aTrans,aClone(aRow))
	aSize(aRow,0)
Next

Return .T.

// ----------------------------------------------------
// Plota a imagem da area interna de transferencia na coordenada indicada

METHOD Paste(L1,C1)                CLASS ZBITMAP
Local nL , nC

// Valida a area de transferencis
If empty(::aTrans)
	::cError := "Empty Transfer Area"
	Return .F.
Endif

// Valida as cordenadas
IF L1 < 0 .or. L1 >= ::nHeight
	::cError := "Invalid Target Line -- Out Of Image Area"
	Return .F.
ElseIf C1 < 0 .or. C1 >= ::nWidth
	::cError := "Invalid Target Column -- Out Of Image Area"
	Return .F.
Endif
                       
// Plota a imagem da area de transferencia
// Validando as coordenadas de colagem caso 
// a imagem colada nas coordenadas saia 
// "fora" da área total da imagem 
For nL := 0 to len(::aTrans)-1
	IF L1+nL < ::nHeight
		For nC := 0 to len(::aTrans[nL+1])-1
			If C1+nC < ::nWidth
				::aMatrix[L1+nL+1][C1+nC+1] := ::aTrans[nL+1][nC+1]
			Else
				EXIT
			Endif
		Next
	Else
		EXIT
	Endif
Next

Return .T.

A área de transferência pode ser compartilhada entre dois objetos de bitmap, bastando atribuir a propriedade ::aTrans. Os métodos CUT(), COPY() e CLEAR(), caso não recebam coordenadas, assumem por default a área total da imagem. Estes métodos são úteis quando precisamos mesclar partes da mesma imagem ou de outra imagem com a atual, ou mesmo para fazer um mosaico — cria-se uma pequena área da imagem com um mosaico, tira-se uma cópia e loops para colar esta parte da imagem de forma distribuída — igual ao efeito “lado a lado” de imagens de fundo do Windows.

Flip horizontal

Uma imagem ou parte dela pode ser invertida horizontalmente com este recurso. Por exemplo, uma imagem com uma foto de todas as pessoas olhando para a direita, pode ser invertida como se fosse uma transparência, e todos agora olham para a esquerda. Porém, se houver um texto na imagem, ele também será “invertido”.

// Inverte horizontalmente uma área da imagem
// Ou a imagem inteira caso a área nao seja especificada
METHOD FlipH(L1,C1,L2,C2) CLASS ZBITMAP
Local nL , nC 
Local nCol, nSwap
IF pCount() == 0
  // Faz flip horizontal da imagem inteira
  L1 := 0
  C1 := 0
  L2 := ::nHeight-1
  C2 := ::nWidth-1
Else
  // Valida coordenadas informados
  IF L1 < 0 .or. L1 >= ::nHeight
    ::cError := "Invalid 1o Line -- Out Of Image Area"
    Return .F.
  ElseIF L2 < 0 .or. L2 >= ::nHeight
    ::cError := "Invalid 2o Line -- Out Of Image Area"
    Return .F.
  ElseIf C1 < 0 .or. C1 >= ::nWidth
    ::cError := "Invalid 1o Column -- Out Of Image Area"
    Return .F.
  ElseIf C2 < 0 .or. C2 >= ::nWidth
    ::cError := "Invalid 2o Column -- Out Of Image Area"
    Return .F.
  ElseIf L1 > L2
    ::cError := "Invalid Lines -- Order mismatch"
    Return .F.
  ElseIf C1 > C2
    ::cError := "Invalid Columns -- Order mismatch"
    Return .F.
  Endif
Endif

For nL := L1+1 to L2+1
  nCol := C2+1
  For nC := C1 + 1 TO C1 + INT( ( C2-C1 ) / 2 ) + 1
    ZSWAP( ::aMatrix[nL][nC] , ::aMatrix[nL][nCol] , nSwap )
    nCol--
  Next
Next

Return .T.

E este recurso é muito útil, principalmente para inverter propositalmente textos que devem ser lidos com um espelho. Por exemplo, a palavra “AMBULÂNCIA” pintada ou adesivada na parte frontal do devido veículo está horizontalmente invertida (ou “ao contrário”), para justamente ser lida de forma correta quando um motorista avistar este veículo no retrovisor. Veja abaixo a imagem original e invertida.

ambulancia

ambulancia_mirror

 

Conclusão

Usando a representação da imagem como array, e algumas funções de array do AdvPL fica fácil editar a imagem e realizar operações dessa natureza. Durante os testes e implementações, foram identificados falhas na gravação de bitmaps lidos do disco, os fontes atualizados da classe estão no GITHUB!

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

Referências

 

 

 

 

Bitmaps em AdvPL – Parte 05

Introdução

No post anterior, vimos a implementação do algoritmo de pintura. Agora vamos ver mais algumas implementações, como por exemplo o traçado de um retângulo (ou um quadrado), a partir das coordenadas informadas, e como é possível gerar a imagem “negativa” a partir da imagem original — igual o negativo de uma fotografia, por exemplo — para quem é do tempo das máquinas fotográficas de filme 😀

Método RECTANGLE

Traçar um retângulo na horizontal ou vertical é um processo mais simples do que traçar uma reta — embora sejam 4 (quatro) retas que formam um retângulo. A implementação deste método traça um retângulo, sem nenhuma “inclinação”. Devido a características desejáveis desta implementação, ele não usa internamente o método Line() — para traçar retas. Depois do código vamos ver por que.

// ---------------------------------------------------
// Desenha um retängulo na cor e espessura especificadas
// Cor e espessura sao opcionais
// Espessura > 1 , preenche a área interna do retängulo 

METHOD Rectangle(L1,C1,L2,C2,nColor,nPen)  CLASS ZBITMAP
Local nL , nC

If nPen = NIL 
	nPen := ::nPenSize
Endif

For nPen := 0 to nPen-1

	// Espessura de linha de retangulo sempre 
	// para a área interna do Retangulo 

	// Traça linhas horizontais
	For nC := C1+nPen to C2-nPen
		::SetPixel(L1+nPen,nC,nColor,1)
		::SetPixel(L2-nPen,nC,nColor,1)
	Next
	
	// Traca as linhas verticais
	For nL := L1+nPen to L2-nPen
		::SetPixel(nL,C1+nPen,nColor,1)
		::SetPixel(nL,C2-nPen,nColor,1)
	Next
	
Next

Return

Uma vez informadas as coordenadas dos extremos do retângulo — por exemplo o ponto superior esquerdo e o ponto inferior direito — a função limita o retângulo nestas coordenadas, e de acordo com a grossura do traço escolhido no parâmetro nPen, as retas adicionais são traçadas ocupando os pontos da área interna do retângulo. Por hora o método ainda não suporta preenchimento, mas quando for implementado, ele não vai usar o método de pintura — a implementação vai ser mais leve e mais simples, apenas preenchendo em sequencia todos os pontos da área interna com a cor desejada.

Método NEGATIVE

Normalmente usado para inverter as cores de uma imagem obtida através de um filme fotográfico do tipo “filme negativo” — o mais conhecido para uso em fotografia. Partindo de uma matriz de cores formadas pela composição RGB — mistura de concentrações diferentes das cores Vermelha (R=Red), Verde (G=Green) e Azul (B-Blue), com intensidade variando de 0 a 255, o negativo de uma cor é obtido com a subtração de 255 menos a intensidade atual de cada cor. Por exemplo: RGB 0,0,0 é a cor preta — nenhum tom das cores. Já a cor branca é a mistura das três cores em tom máximo (RGB 255,255,255). A cor vermelha é representada por RGB 255,0,0 , e a sua cor negativa é o Ciano — obtido através da subtração de 255 de cada uma das cores — RGB 0,255,255.

Como cada bitmap colorido possui uma tabela de cores logo após o header,  o efeito negativo pode ser obtido apenas refazendo a tabela de cores usando a aritmética mencionada acima. Assim, o método fica:

// --------------------------------------------------------
// Faz o "negativo" da imagem 
// Recalcula as cores complementares da tabela de cores

METHOD Negative()  CLASS ZBITMAP
Local nI
For nI := 1 to len(::aColors)
	::aColors[nI][1] := 255-::aColors[nI][1]
	::aColors[nI][2] := 255-::aColors[nI][2]
	::aColors[nI][3] := 255-::aColors[nI][3]
Next
Return

Por exemplo, a imagem negativa da bandeira do brasil fica assim:

bandeiraneg

Conclusão

Com mais alguns métodos, a classe ZBITMAP vai virar um “PaintBrush” em AdvPL 😀

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

Referências

 

QR Code em AdvPL – Parte 02

Introdução

O post anterior foi apenas para abrir o apetite. Agora nós vamos entrar no passo a passo da classe ZQRCODE.

Classe ZQRCODE

A implementação mais simples para a utilização do usuário normalmente é a mais complexa de ser implementada. Simplificar uma ponta normalmente significa puxar a dificuldade e ter mais etapas e processos na outra. Na Internet existem várias implementações de sites gratuitos com geradores online de QR Code.

Boa parte delas permite gerar um QR Code solicitando ao usuário apenas um link, um email, um texto livre. Outras já são mais sofisticadas, permitem ao usuário informar o nível de correção de erro da imagem — quanto maior o nível, mais módulos (os quadradinhos) são usados para desenhar a imagem.

Criando o Objeto ZQRCODE

O método NEW(), construtor da classe, não recebe parâmetros, apenas cria um objeto vazio para a confecção de um QR Code. Por default, a correção de erro utilizada é “L” (Low) — a menor e mais leve. Por hora basta saber que existem 4 níveis de correção, e que isso pode ser alterado por um método na instância do objeto.

Setando a correção de erro

Através do método SETEC(), podemos alterar o nível da correção de erros da imagem a ser gerada. São 4 níveis, L (Low) , M (Medium), Q (Quartile) e H (High). Cada um dos níveis permite um índice de recuperação aproximado — variando de 7% (L) a 30% (H) — na prática, isto indica o percentual aproximando de “o quanto uma imagem pode estar danificada mas ainda assim ser lida”. Este recurso permite por exemplo a criação de QR Codes “artísticos”, por exemplo com uma outra imagem ou logotipo sobrescrevendo uma parte do código, e mesmo assim ele é lido com sucesso.

Informando os dados

Através do método SETDATA() podemos definir qual a informação que será colocada dentro da área de dados do QR Code. Embora exista suporte específico para dados numéricos, a implementação em AdvPL recebe sempre uma string. Como o QR Code possui 4 modos de codificação de dados (numérico, alfanumérico restrito, bytes e Kanji), você pode opcionalmente informar um número de 1 a 4 no modo desejado, ou não informar este parâmetro — a classe decide o melhor método baseado nas informações fornecidas. E, apenas para informação, Kanji não foi implementado 😀

Montando o QR Code

O método BUILDDATA() é onde começa realmente a implementação. Baseado nas escolhas anteriores e nos dados fornecidos, este método concentra chamadas para vários métodos e funções auxiliares. Uma das primeiras é a decisão de qual modo serão codificados os dados — numérico, alfanumérico ou bytes.

Como o modo alfanumérico contempla apenas letras maiúsculas, números e os caracteres  ” “ (espaço em branco), “$” (cifrão) , “%” (percentual), “*” (asterisco), “+” (soma), “-“ (subtração), “.” (ponto), “/” (barra direita) e “:” (dois pontos), ele é suficiente para um endereço de um Web Site, ou mesmo um e-mail — mesmo não tendo o caractere “@” ( arroba) neste alfabeto, nada impede de usar uma escape sequence de URL “%40” no lugar deste caractere — onde a interpretação depende do leitor — ou codificar em bytes UTF8 ( onde o “@” ‘é o caractere ASCII 64 ).

Escolhido o alfabeto, e assumindo um nível de correção de erro, podemos estimar qual é a versão de QR Code necessária para que o dado informado “caiba” dentro. A classe possui uma lista predefinida de capacidade por nível de correção de erro e alfabetos, logo uma busca ordenada nesta lista é suficiente para encontrar a versão de QR Code adequada.

Determinada a versão — e consequentemente o tamanho — do QR Code, a propriedade ::aGrid é inicializada com um array bidimensional de números, onde cada posição equivale a um módulo (quadradinho) do QR Code. Todas as posições do array são inicializadas com o valor -1 — que indica que esta posição está livre para uso — e posteriormente as áreas reservadas e de dados serão preenchidas com 0 (módulo vazio) ou 1 (módulo usado).

Áreas reservadas

Um QR Code é lido por um scanner óptico, de uma forma diferente de um código de barras tradicional. Para ser possível a leitura dos dados na ordem correta, e permitir ao leitor determinar a localização e alinhamento dos dados. O padrão de módulos chamado de “localizador” é uma imagem composta por uma matriz 7×7 formando um quadrado com 26 módulos, e na área interna 9 módulos no centro formando outro quadrado.

Um QR Code utiliza três localizadores, distribuídos nos cantos superior esquerdo, superior direito e inferior esquerdo. A partir da versão 2, é necessário utilizar uma imagem adicional, chamada de “alinhamento”. Ele é formado por uma matriz 5×5 formando um quadrado de 18 módulos, com um módulo central. Quanto maior a versão do QR Code, e consequentemente seu tamanho, mais imagens de alinhamento são necessárias.

Veja abaixo o padrão localizador e o padrão de alinhamento, respectivamente:

qr_Pattern01        qr_Pattern02

Em volta de cada localizador, deve ser deixada em branco uma margem de um módulo. O localizador é 7×7, mas ele reserva uma área de 8×8 módulos, onde a linha e coluna de margem de módulos não setados está na parte de dentro do QR Code.

Os três localizadores são ligados entre si por uma sequência alternada de módulos ligados e desligados, formando uma espécie de “linha pontilhada”. Os módulos que compreendem estas linhas também são reservados. Estas linhas pontilhadas são chamadas de “Timing Patterns“. E, existe mais um ponto reservado do lado do localizador inferior, chamado “Dark Module“, deve ser setado na nona coluna da esquerda para a direita do localizador inferior, imediatamente a direita da primeira linha do localizador inferior. Veja abaixo um QR Code versão 1 com os localizadores e as “Timing Lines” e o “Dark Module” desenhados.

qr_Pattern03

Ainda faz parte das áreas reservadas 30 módulos, em volta do localizador superior esquerdo, na lateral direita do localizador inferior esquerdo, e na primeira linha abaixo do localizador superior direito. Estes módulos são reservados para representar a formatação e correção de erro, que usam 15 módulos. São reservados 30 módulos, para a informação ser colocada duas vezes na imagem. Estes espaços estão destacados em azul. Posteriormente vamos ver como estas áreas são preenchidas.

Codificação dos dados

Uma vez com a definição do alfabeto a ser usado, os dados fornecidos são codificados em uma sequência de bits. Caso a sequencia de bits seja menor do que a área de dados (quantidade de módulos não reservados disponíveis), são usados dois bytes predefinidos para preencher a sequência até o final. Após a codificação binária, cada conjunto de 8 bits (1 byte) é convertido para um valor inteiro de 0 a 255, que na prática compõe a sequencia de dados da mensagem.

Porém esta sequência não é desenhada diretamente no QR Code. De acordo com a versão e do nível de correção de erro utilizado, um determinado número de bytes de correção de erro devem ser gerados usando a sequencia de dados da mensagem e um polinômio (equação) gerada sob medida para a quantidade de bytes de correção de erro. Neste ponto do processamento, chamamos cada um destes “bytes” ou números de “Code Words”.

Geração da correção de erro

Esta é uma das etapas mais complexas do processo. No tutorial mais completo que eu encontrei, as equações e as etapas — multiplicação e divisão de polinômios — era explicada em detalhes, até mesmo como funciona a geração do polinômio de acordo com o número de Code Words necessárias para correção de erro.

A primeira grande sacada foi aproveitar um recurso de montagem dinâmica das equações para cada número de bytes de correção de erro necessários, e guardar as equações em uma lista, assim eu já tenho no programa as equações prontas.

A segunda grande sacada foi aproveitar uma lista pronta de conversão de dados do polinômio em uma notação de potência de dois — ou notação Alpha — também já calculada. Como existe a necessidade durante o processamento de transformar uma equação com os valores numéricos dos elementos entre notações, foram montadas duas funções de conversão com a lista (Alpha para Numérico e vice-versa).

A terceira grande sacada foi, após entender como os cálculos funcionam, que era possível representar os polinômios com arrays bidimensionais, e que as operações poderiam ser feitas apenas com os valores e as potências armazenados nestes arrays. Uma vez que eu entendi a regra, aí sim foi possível escrever o resto da rotina.

Depois de multiplicações e divisões e conversões, o método BUIDERRDATA() retorna um array contendo os números (bytes ou Code Words) que completam a sequência de Code Words dos dados.

Disposição dos dados na matriz

Chegando na penúltima etapa, após montar as sequencias de dados e de correção de erro, a próxima etapa é gerar um (ou mais) grupos de bits com todos estes números e distribuir de forma ordenada na matriz do QR Code, sem passar por cima das áreas reservadas. Esta etapa não é muito complexa, porém pode exigir a separação e intercalação de dados, de acordo com o tamanho (versão) do QR Code a ser gerado.

Na forma mais simples, com QR Code que usam apenas um grupo de informações, a sequência final de CodeWords é gerada apenas acrescentando a correção de erro no final da sequência de dados, gerando uma string binária (apenas com “0” e “1”) e distribuindo pares de bits da direita para a esquerda, iniciando no canto inferior direito, de baixo para cima, e invertendo a direção e pulando duas colunas para a esquerda a cada vez que o limite superior ou inferior são atingidos. É praticamente um ziguezague de ziguezagues. E ainda não acabou.

Mascaramento de Dados

Dependendo da sequência de bits que compõe a mensagem, e da forma que eles foram distribuídos dentro do QR Code, pode acontecer agrupamentos de muitos módulos preenchidos, junto com áreas em branco, ou ainda áreas de dados que podem ficar parecidas com as áreas reservadas e “confundirem” o scanner.

Para evitar este tipo de ocorrência, existem 8 fórmulas de “mascaramento de dados”, criadas para serem aplicadas após a disposição dos dados, para verificar como ficou a versão final do QR Code. Existe uma técnica que envolve algumas regras analisar as sequencias e áreas de dados do QR Code, para determinar um fator de “penalidade”.

A etapa de mascaramento de dados consiste em guardar a versão original dos dados distribuídos no QR Code, gerar todas as 8 possibilidades de aplicação de mascaramento de dados, e avaliar o índice de penalidade de cada uma. O mascaramento que retornar o menor índice é a melhor opção de uso para facilitar a leitura pelo scanner. Este trabalho é feito pelo método MaskDataGrid()

Informações de versão e formato

Após escolher o mascaramento a ser usado, e já ter as áreas de dados e reservadas plotadas, é possível determinar aquela sequência de formato de 15 módulos que eu mencionei anteriormente, para finalmente colocá-la no grid do QR Code. Com isso feito, o resultado final é o array ::aGrid preenchido com zeros e uns, onde cada 0 é um módulo em branco — vazio — e cada 1 é um módulo a ser desenhado. Com este array, é possível criar um objeto ZBITMAP monocromático, e fazer a pintura de blocos de pontos para formar os quadradinhos necessários, lembrando que a imagem final deve reservar 5 módulos de margem (superior, inferior e laterais) em branco.

Conclusão

Nos próximos posts sobre QR Code, vou detalhar cada etapa deste processo, desde a escolha do alfabeto ou modo de codificação, até a geração da imagem final, com o código, claro !!!!

Espero que vocês tenham gostado, e desejo a todos TERABYTES DE SUCESSO !!!

Referências