Neste artigo veremos o uso prático de uma biblioteca chamada de Zxing, que tem por objetivo trabalhar com código de barras 1D e 2D. Mas, primeiro, devemos entender o que é um código de barras e como ele funciona.
O Código de Barras é uma representação gráfica de dados numéricos ou alfanuméricos, estes estão presentes com muita frequência no nosso dia a dia e tem a representação como na Figura 1.
A decodificação ou leitura da Figura 1 é realizada da seguinte forma: um scanner (leitor de código de barras) emite um raio vermelho que percorre todas as barras e onde a barra for escura, a luz é absorvida, e onde for branca (espaços em branco) a luz é refletida para o scanner e no computador é feita a conversão para números ou letras.
Existem diversos formatos de código de barras, por exemplo: Código 39, EAN-13, GS1-128, DUN-14 e etc. Cada um destes tem sua especificidade, por exemplo: o código 39 ou Code 39 possibilita a codificação de letras e números e mais alguns símbolos especiais.
Obviamente que o conceito apresentado aqui foi apenas introdutório em relação ao funcionamento do código de barras para que você leitor possa continuar lendo o restante do artigo com mais confiança no assunto tratado. Existem muitas mais características em relação a cada formatação e como elas são feitas, mas não é foco do nosso artigo apresentar tais conceitos e sim o funcionamento na prática da biblioteca Zxing.
Zxing
Nem sempre temos o leitor de código de barras (scanner) em mãos ou temos situações em que precisamos dispensar o seu uso e é aqui que entra a API Zxing. Para entender melhor, vamos a um cenário real: “O seu sistema deve possuir um módulo onde o usuário fará o upload de contratos assinados pelo seu cliente (imagens) e o sistema deve associar automaticamente estas imagens com o cliente correto sem nenhuma intervenção humana. Na capa do contrato, que pode possuir N folhas, há um código de barras que identifica a numeração do contrato (a numeração do contrato possui o código do cliente em sua composição)."
Sendo assim, nós podemos usar o Zxing para “ler” a capa do contrato e extrair a numeração contida no código de barras e após extração da numeração nós podemos buscar o cliente correspondente. Dado o cenário exposto, nós podemos perceber que o Zxing é muito útil quando precisamos ler as informações contidas no código de barras e não temos a disponibilidade de um leitor de código de barras ou mesmo não podemos utilizar, como mostrado no cenário. Neste artigo veremos apenas como fazer a leitura de um código de barras através de uma imagem que contenha tal código, mas vale ressaltar que o Zxing também realiza a criação de código de barras a partir do valor informado.
O primeiro passo para usar a API Zxing no nosso projeto é fazer a importação das bibliotecas necessárias (o link de download está no final do artigo). Coloque estas no classpath do seu projeto para que possa usar as classes que veremos mais a frente.
Vejamos agora como ficará a interface da nossa aplicação na Figura 2.
A nossa aplicação contém quatro componentes que serão de grande utilidade: o primeiro é o campo de texto onde será mostrado o caminho da imagem que selecionamos, o segundo é o resultado do valor extraído do código de barras, o terceiro é o botão 'Selecionar Imagem' que nos possibilita selecionar uma imagem do nosso computador e o quarto botão 'Processar' inicia o processamento do Zxing que fará a leitura do código de barras e extração dos valores necessários.
Agora vejamos como ficou nosso arquivo .form para quem desejar criar a interface apresentada na Figura 2, no Netbeans.
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" type="org.netbeans.modules.form.forminfo
.JFrameFormInfo">
<Properties>
<Property name="defaultCloseOperation" type="int" value="3"/>
</Properties>
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
</SyntheticProperties>
<AuxValues>
<AuxValue name="FormSettings_generateMnemonicsCode"
type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_layoutCodeTarget"
type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle"
type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal"
type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier"
type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jLabel1" alignment="0" min="-2"
max="-2" attributes="0"/>
<Component id="jLabel2" alignment="0" min="-2"
max="-2" attributes="0"/>
<Component id="jTextFieldImagem" alignment="0"
min="-2" pref="329" max="-2" attributes="0"/>
<Component id="jTextFieldResultado" alignment="0"
min="-2" pref="329" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="jButtonSelecionar" min="-2"
max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jButtonProcessar" min="-2"
max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace pref="25" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="jLabel1" min="-2" max="-2"
attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jTextFieldImagem" min="-2"
max="-2" attributes="0"/>
<EmptySpace min="-2" pref="25" max="-2"
attributes="0"/>
<Component id="jLabel2" min="-2" max="-2"
attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jTextFieldResultado" min="-2"
max="-2" attributes="0"/>
<EmptySpace min="-2" pref="20" max="-2"
attributes="0"/>
<Group type="103" groupAlignment="3"
attributes="0">
<Component id="jButtonSelecionar"
alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="jButtonProcessar"
alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="jLabel1">
<Properties>
<Property name="text" type="java.lang.String" value="Imagem"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="jLabel2">
<Properties>
<Property name="text" type="java.lang.String" value="Resultado"/>
</Properties>
</Component>
<Component class="javax.swing.JTextField"
name="jTextFieldImagem">
<Properties>
<Property name="enabled" type="boolean"
value="false"/>
</Properties>
</Component>
<Component class="javax.swing.JTextField"
name="jTextFieldResultado">
</Component>
<Component class="javax.swing.JButton"
name="jButtonSelecionar">
<Properties>
<Property name="text" type="java.lang.String"
value="Selecionar Imagem"/>
</Properties>
<Events>
<EventHandler event="actionPerformed"
listener="java.awt.event.ActionListener"
parameters="java.awt.event.ActionEvent"
handler="jButtonSelecionarActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton"
name="jButtonProcessar">
<Properties>
<Property name="text" type="java.lang.String"
value="Processar"/>
</Properties>
<Events>
<EventHandler event="actionPerformed"
listener="java.awt.event.ActionListener"
parameters="java.awt.event.ActionEvent"
handler="jButtonProcessarActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>
O código acima dispensa comentário, pois ele servirá apenas para que você possa reproduzir a mesma interface no seu ambiente de desenvolvimento, caso seja o Netbeans.
Enfim começaremos a construir os métodos de cada botão acima e depois mostraremos como ficou nossa classe Fzxing por completo.
private void jButtonSelecionarActionPerformed(java.awt.event.ActionEvent evt)
{//GEN-FIRST:event_jButtonSelecionarActionPerformed
JFileChooser fChooser = new JFileChooser();
fChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
if (fChooser.showSaveDialog(this) != 1){
File fImage = fChooser.getSelectedFile();
try {
buffImage = ImageIO.read(fImage);
jTextFieldImagem.setText(fImage.getAbsolutePath());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}//GEN-LAST:event_jButtonSelecionarActionPerformed
O nosso botão jButtonSelecionar possui um listener que dispara o método jButtonSelecionarActionPerformed(), que é chamado toda vez que o usuário clicar no botão. Na Listagem 2 temos exatamente a demonstração do método que possibilita a seleção da imagem. Logo na primeira e segunda linhas temos a criação de um objeto JfileChooser e a configuração do modo de seleção para FILES_ONLY, assim, restringimos a escolha do usuário apenas para arquivos e não diretórios. Continuando a codificação, nós temos uma condição que abre um diálogo do tipo 'saveDialog' e, caso o usuário escolha um arquivo, em vez de clicar em cancelar, o resultado será diferente de 1 e entramos na condição proposta.
Dentro da condição proposta nós capturamos o arquivo escolhido através do fchooser.getSelectedFile() e logo depois transformamos o arquivo, que deve ser uma imagem, em um BufferedImage, não esquecendo de mostrar o caminho da imagem no campo jTextFieldImagem. Todo esse processo faz com que armazenemos a imagem selecionada em um objeto do tipo BufferedImage que está declarado no início da nossa classe Fzxing, ou seja, um atributo de instância.
private void jButtonProcessarActionPerformed(java.awt.event.ActionEvent evt)
{//GEN-FIRST:event_jButtonProcessarActionPerformed
try {
if (buffImage == null){
JOptionPane.showMessageDialog(this,
"Você deve escolher a imagem !!");
return;
}
//1
LuminanceSource source = new BufferedImageLuminanceSource(buffImage);
//2
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
//3
Reader reader = new MultiFormatReader();
//4
Result result = reader.decode(bitmap);
jTextFieldResultado.setText(result.getText());
} catch (NotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ChecksumException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }//GEN-LAST:event_jButtonProcessarActionPerformed
Nós só podemos processar a imagem caso haja alguma, e sendo assim, a primeira checagem da Listagem 3é conferir se o objeto 'buffImage' não é nulo. As próximas linhas são dedicadas a funções exclusivas do Zxing que estão numeradas com linhas para facilitar a explicação:
- As linhas 1 e 2 fazem o tratamento necessário na imagem para só então processá-la em um 'Reader' específico do Zxing. No nosso caso, na linha 3 nós criamos um MultiFormatReader que possibilita a leitura de código de barras em diversos formatos (Code 39, EAN-13 …), mas você pode criar um tipo específico de Reader caso ache necessário.
- Na linha 4 nós usamos o Reader criado na linha 3 para chamar o método 'decode()'que retorna os dados encontrados no código de barras, O retorno deste método é um Result que contém diversos método como o getText(), que retorna o dado em forma de string, e o getBarcodeFormat() que retorna o formato do código de barras e etc.
Para finalizar o artigo, iremos mostrar na Listagem 4 o código completo da classe Fzxing com todos os detalhes e implementações explicados anteriormente, para que você leitor possa utilizá-la como meio de estudo.
package br.com.dev.gui;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.FormatException;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
public class FZxing extends javax.swing.JFrame {
private BufferedImage buffImage;
/** Creates new form FZxing */
public FZxing() {
initComponents();
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc=" Código Gerado
">//GEN-BEGIN:initComponents
private void initComponents() {
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
jTextFieldImagem = new javax.swing.JTextField();
jTextFieldResultado = new javax.swing.JTextField();
jButtonSelecionar = new javax.swing.JButton();
jButtonProcessar = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jLabel1.setText("Imagem");
jLabel2.setText("Resultado");
jTextFieldImagem.setEnabled(false);
jButtonSelecionar.setText("Selecionar Imagem");
jButtonSelecionar.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButtonSelecionarActionPerformed(evt);
}
});
jButtonProcessar.setText("Processar");
jButtonProcessar.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButtonProcessarActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout
.Alignment.LEADING)
.addComponent(jLabel1)
.addComponent(jLabel2)
.addComponent(jTextFieldImagem, javax.swing.GroupLayout
.PREFERRED_SIZE, 329, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jTextFieldResultado, javax.swing.GroupLayout
.PREFERRED_SIZE, 329, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createSequentialGroup()
.addComponent(jButtonSelecionar)
.addPreferredGap(javax.swing.LayoutStyle
.ComponentPlacement.RELATED)
.addComponent(jButtonProcessar)))
.addContainerGap(25, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jTextFieldImagem, javax.swing.GroupLayout.PREFERRED_SIZE,
javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(25, 25, 25)
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jTextFieldResultado, javax.swing.GroupLayout.PREFERRED_SIZE,
javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(20, 20, 20)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jButtonSelecionar)
.addComponent(jButtonProcessar))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pack();
}// </editor-fold>//GEN-END:initComponents
private void jButtonProcessarActionPerformed(java.awt.event.ActionEvent evt)
{//GEN-FIRST:event_jButtonProcessarActionPerformed
try {
if (buffImage == null){
JOptionPane.showMessageDialog(this, "Você deve escolher a imagem !!");
return;
}
LuminanceSource source = new BufferedImageLuminanceSource(buffImage);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Reader reader = new MultiFormatReader();
Result result = reader.decode(bitmap);
jTextFieldResultado.setText(result.getText());
} catch (NotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ChecksumException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }//GEN-LAST:event_jButtonProcessarActionPerformed
private void jButtonSelecionarActionPerformed(java.awt.event.ActionEvent evt)
{//GEN-FIRST:event_jButtonSelecionarActionPerformed
JFileChooser fChooser = new JFileChooser();
fChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
if (fChooser.showSaveDialog(this) != 1){
File fImage = fChooser.getSelectedFile();
try {
buffImage = ImageIO.read(fImage);
jTextFieldImagem.setText(fImage.getAbsolutePath());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}//GEN-LAST:event_jButtonSelecionarActionPerformed
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FZxing().setVisible(true);
}
});
}
// Declaração de variáveis - não modifique//GEN-BEGIN:variables
private javax.swing.JButton jButtonProcessar;
private javax.swing.JButton jButtonSelecionar;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JTextField jTextFieldImagem;
private javax.swing.JTextField jTextFieldResultado;
// Fim da declaração de variáveis//GEN-END:variables
}
Este artigo teve como principal objetivo demonstrar o uso da API Zxing de forma prática, rápida e eficaz. Assim, você caro leitor, poderá usar tal biblioteca em seus projetos que precisam realizar leitura ou escrita de código de barras em Java.