使用zxing进行QR码编解码

38

好的,那么我假设这里可能有人之前使用过zxing库。我正在开发一个Java应用程序,其中一个需求是将一组字节数组编码为QR码并在以后解码它。

以下是我的编码器的示例代码:

byte[] b = {0x48, 0x45, 0x4C, 0x4C, 0x4F};
//convert the byte array into a UTF-8 string
String data;
try {
    data = new String(b, "UTF8");
}
catch (UnsupportedEncodingException e) {
 //the program shouldn't be able to get here
 return;
}

//get a byte matrix for the data
ByteMatrix matrix;
com.google.zxing.Writer writer = new QRCodeWriter();
try {
 matrix = writer.encode(data, com.google.zxing.BarcodeFormat.QR_CODE, width, height);
}
catch (com.google.zxing.WriterException e) {
 //exit the method
 return;
}

//generate an image from the byte matrix
int width = matrix.getWidth(); 
int height = matrix.getHeight(); 

byte[][] array = matrix.getArray();

//create buffered image to draw to
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

//iterate through the matrix and draw the pixels to the image
for (int y = 0; y < height; y++) { 
 for (int x = 0; x < width; x++) { 
  int grayValue = array[y][x] & 0xff; 
  image.setRGB(x, y, (grayValue == 0 ? 0 : 0xFFFFFF));
 }
}

//write the image to the output stream
ImageIO.write(image, "png", outputStream);

这段代码中的初始字节数组仅用于测试。实际的字节数据将会有所不同。

以下是我的解码器的外观:

//get the data from the input stream
BufferedImage image = ImageIO.read(inputStream);

//convert the image to a binary bitmap source
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

//decode the barcode
QRCodeReader reader = new QRCodeReader();

Result result;
try {
 result = reader.decode(bitmap, hints);
} catch (ReaderException e) {
 //the data is improperly formatted
 throw new MCCDatabaseMismatchException();
}

byte[] b = result.getRawBytes();
System.out.println(ByteHelper.convertUnsignedBytesToHexString(result.getText().getBytes("UTF8")));
System.out.println(ByteHelper.convertUnsignedBytesToHexString(b));

convertUnsignedBytesToHexString(byte)是一种将字节数组转换为十六进制字符字符串的方法。

当我尝试同时运行这两个代码块时,输出结果如下:

48454c4c4f
202b0b78cc00ec11ec11ec11ec11ec11ec11ec

显然这段文本正在被编码,但实际的数据字节完全不对。希望能在这里得到帮助。


captureActivity从摄像头捕获QR码图像,解码后根据QR码中存储的数据类型显示结果。例如,如果网站URL被编码在QR码中,则结果屏幕将有一个按钮打开该URL,类似于此类操作。我需要从SD卡读取图像,进行解码,并以与captureActivity相同的方式处理输出zxing解码的情况。在获得“Result result”输出后,我需要做什么? - Kulin Choksi
你应该发布一个新的问题,附上你代码的示例。这样做会得到比我在这里提供的更好的答案。 - LandonSchropp
6个回答

53

因此,对于任何不想花费两天时间在互联网上搜索以找出答案的人来说,当您将字节数组编码为QR码时,必须使用ISO-8859-1字符集,而不是UTF-8


2
不是真的 - 请看我的答案,附带了关于UTF-8、zxing qrencoder的工作代码,我试过的每个iPhone解码器都可以工作。 - Shaybc
10
这其实是一个很好的答案。QR码规范只允许ISO-8859-1编码。解码器有时可以正确猜测UTF-8,但并不总是能够正确识别。 - Sean Owen
1
没错。当我使用“UTF-8”时,我的应用程序在一些安卓和iPhone扫描仪上工作,但不是所有的。当我将编码更改为“ISO-8859-1”时,所有的扫描仪/解码器都能够扫描编码的QR图像。 - Khushboo
这个答案部分正确,但是上下文不对。因为这是10年前的问题,在那个时候zxing还没有实现ResultMetadataType.BYTE_SEGMENTS。所以在那个时候,提取未应用启发式算法的原始内容的唯一方法是计算长度字段中的位数(请参见维基百科)。使用计算出的长度字段,其后的位是通过4位移位得到的原始内容。要计算长度字段,您必须按照QR码根据ECC和数据大小分配大小的完全相同的算法进行操作。 - daparic

19

以下是使用ZXing进行UTF-8编码的QR码的Java代码示例,注意:您需要将路径和UTF-8数据更改为您的路径和语言字符

package com.mypackage.qr;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Hashtable;

import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.*;

public class CreateQR {

public static void main(String[] args)
{
    Charset charset = Charset.forName("UTF-8");
    CharsetEncoder encoder = charset.newEncoder();
    byte[] b = null;
    try {
        // Convert a string to UTF-8 bytes in a ByteBuffer
        ByteBuffer bbuf = encoder.encode(CharBuffer.wrap("utf 8 characters - i used hebrew, but you should write some of your own language characters"));
        b = bbuf.array();
    } catch (CharacterCodingException e) {
        System.out.println(e.getMessage());
    }

    String data;
    try {
        data = new String(b, "UTF-8");
        // get a byte matrix for the data
        BitMatrix matrix = null;
        int h = 100;
        int w = 100;
        com.google.zxing.Writer writer = new MultiFormatWriter();
        try {
            Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>(2);
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            matrix = writer.encode(data,
            com.google.zxing.BarcodeFormat.QR_CODE, w, h, hints);
        } catch (com.google.zxing.WriterException e) {
            System.out.println(e.getMessage());
        }

        // change this path to match yours (this is my mac home folder, you can use: c:\\qr_png.png if you are on windows)
                String filePath = "/Users/shaybc/Desktop/OutlookQR/qr_png.png";
        File file = new File(filePath);
        try {
            MatrixToImageWriter.writeToFile(matrix, "PNG", file);
            System.out.println("printing to " + file.getAbsolutePath());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    } catch (UnsupportedEncodingException e) {
        System.out.println(e.getMessage());
    }
}

}

这段代码在我的端上也能运行。虽然,我的代码并不完全相同。我没有保存由encode()返回的图像。但是,我将BitMatrix转换为BitMap实例。 - Khushboo
2
Shaybc,你编码的QR图像可能可以被一些安卓或iPhone应用扫描,但不是所有应用都可以。为了成功地进行QR图像编码,你需要使用“ISO-8859-1”而不是“UTF-8”。你可以通过使用Red Laser App或QR Droid应用程序从Google Play测试你编码的QR图像。 - Khushboo
2
刚刚检查了Red Laser和QR Droid,它们都成功读取UTF8编码的数据。 - GetUsername
这个回答与问题无关。 - daparic

6

就我所知,我的Groovy Spike似乎可以处理UTF-8和ISO-8859-1字符编码。不确定当非zxing解码器尝试对UTF-8编码的图像进行解码时会发生什么...可能会因设备而异。

// ------------------------------------------------------------------------------------
// Requires: groovy-1.7.6, jdk1.6.0_03, ./lib with zxing core-1.7.jar, javase-1.7.jar 
// Javadocs: http://zxing.org/w/docs/javadoc/overview-summary.html
// Run with: groovy -cp "./lib/*" zxing.groovy
// ------------------------------------------------------------------------------------

import com.google.zxing.*
import com.google.zxing.common.*
import com.google.zxing.client.j2se.*

import java.awt.image.BufferedImage
import javax.imageio.ImageIO

def class zxing {
    def static main(def args) {
        def filename = "./qrcode.png"
        def data = "This is a test to see if I can encode and decode this data..."
        def charset = "UTF-8" //"ISO-8859-1" 
        def hints = new Hashtable<EncodeHintType, String>([(EncodeHintType.CHARACTER_SET): charset])

        writeQrCode(filename, data, charset, hints, 100, 100)

        assert data == readQrCode(filename, charset, hints)
    }

    def static writeQrCode(def filename, def data, def charset, def hints, def width, def height) {
        BitMatrix matrix = new MultiFormatWriter().encode(new String(data.getBytes(charset), charset), BarcodeFormat.QR_CODE, width, height, hints)
        MatrixToImageWriter.writeToFile(matrix, filename.substring(filename.lastIndexOf('.')+1), new File(filename))
    }

    def static readQrCode(def filename, def charset, def hints) {
        BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(ImageIO.read(new FileInputStream(filename)))))
        Result result = new MultiFormatReader().decode(binaryBitmap, hints)

        result.getText()        
    }

}

1

我尝试使用第一个答案中提到的ISO-8859-1。在编码方面一切都很顺利,但是当我尝试使用解码后的结果字符串获取byte[]时,所有负字节都变成了字符63(问号)。以下代码无法正常工作:

// Encoding works great
byte[] contents = new byte[]{-1};
QRCodeWriter codeWriter = new QRCodeWriter();
BitMatrix bitMatrix = codeWriter.encode(new String(contents, Charset.forName("ISO-8859-1")), BarcodeFormat.QR_CODE, w, h);

// Decodes like this fails
LuminanceSource ls = new BufferedImageLuminanceSource(encodedBufferedImage);
Result result = new QRCodeReader().decode(new BinaryBitmap( new HybridBinarizer(ls)));
byte[] resultBytes = result.getText().getBytes(Charset.forName("ISO-8859-1")); // a byte[] with byte 63 is given
return resultBytes;

看起来很奇怪,因为在一个非常旧的版本中(不确定是哪个版本),API有一个方法可以很好地工作:

Vector byteSegments = result.getByteSegments();

因此,我尝试搜索为什么此方法被删除,并意识到可以通过元数据获取ByteSegments。 因此,我的解码方法如下:

// Decodes like this works perfectly
LuminanceSource ls = new BufferedImageLuminanceSource(encodedBufferedImage);
Result result = new QRCodeReader().decode(new BinaryBitmap( new HybridBinarizer(ls)));
Vector byteSegments = (Vector) result.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS);  
int i = 0;
int tam = 0;
for (Object o : byteSegments) {
    byte[] bs = (byte[])o;
    tam += bs.length;
}
byte[] resultBytes = new byte[tam];
i = 0;
for (Object o : byteSegments) {
    byte[] bs = (byte[])o;
    for (byte b : bs) {
        resultBytes[i++] = b;
    }
}
return resultBytes;

这是最恰当的参考答案。 - daparic

1
也许值得看看QRGen,它是基于ZXing构建的,并支持UTF-8以此类语法。
// if using special characters don't forget to supply the encoding
VCard johnSpecial = new VCard("Jöhn Dɵe")
                        .setAdress("ëåäöƞ Sträät 1, 1234 Döestüwn");
QRCode.from(johnSpecial).withCharset("UTF-8").file();

1
如果您真的需要编码UTF-8,可以尝试在前面添加Unicode字节顺序标记。我不知道这种方法的支持有多广泛,但至少ZXing似乎支持它:http://code.google.com/p/zxing/issues/detail?id=103 最近我一直在研究QR模式,我认为我在其他地方看到过同样的做法,但我不知道具体在哪里。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接