QR Code encoding and decoding using zxing

108,344

Solution 1

So, for future reference for anybody who doesn't want to spend two days searching the internet to figure this out, when you encode byte arrays into QR Codes, you have to use the ISO-8859-1character set, not UTF-8.

Solution 2

this is my working example Java code to encode QR code using ZXing with UTF-8 encoding, please note: you will need to change the path and utf8 data to your path and language characters

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());
    }
}

}

Solution 3

For what it's worth, my groovy spike seems to work with both UTF-8 and ISO-8859-1 character encodings. Not sure what will happen when a non zxing decoder tries to decode the UTF-8 encoded image though... probably varies depending on the device.

// ------------------------------------------------------------------------------------
// 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()        
    }

}

Solution 4

If you really need to encode UTF-8, you can try prepending the unicode byte order mark. I have no idea how widespread the support for this method is, but ZXing at least appears to support it: http://code.google.com/p/zxing/issues/detail?id=103

I've been reading up on QR Mode recently, and I think I've seen the same practice mentioned elsewhere, but I've not the foggiest where.

Solution 5

I tried using ISO-8859-1 as said in the first answer. All went ok on encoding, but when I tried to get the byte[] using result string on decoding, all negative bytes became the character 63 (question mark). The following code does not work:

// 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;

It looks so strange because the API in a very old version (don't know exactly) had a method thar works well:

Vector byteSegments = result.getByteSegments();

So I tried to search why this method was removed and realized that there is a way to get ByteSegments, through metadata. So my decode method looks like:

// 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;
Share:
108,344
LandonSchropp
Author by

LandonSchropp

I'm a developer, designer and entrepreneur based in Kansas City. I'm passionate about building simple apps people love to use.

Updated on February 06, 2020

Comments

  • LandonSchropp
    LandonSchropp about 4 years

    Okay, so I'm going to take the off chance that someone here has used zxing before. I'm developing a Java application, and one of the things it needs to do is encode a byte array of data into a QR Code and then decode it at a later time.

    Here's an example of what my encoder looks like:

    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);
    

    The beginning byte array in this code is just used to test it. The actual byte data will be varied.

    Here's what my decoder looks like:

    //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) is a method which converts an array of bytes in a string of hexadecimal characters.

    When I try to run these two blocks of code together, this is the output:

    48454c4c4f
    202b0b78cc00ec11ec11ec11ec11ec11ec11ec
    

    Clearly the text is being encoded, but the actual bytes of data are completely off. Any help would be appreciated here.

  • Shaybc
    Shaybc over 12 years
    not true - please see my answer with attached WORKING code about UTF-8 wi zxing qrencoder, and every decoder i tried from iphone worked
  • Sean Owen
    Sean Owen over 12 years
    That's actually a good answer. The QR code spec doesn't allow anything but ISO-8859-1. Decoders happen to guess UTF-8 correctly sometimes, but can't always get it right.
  • Khushboo
    Khushboo about 12 years
    This piece of code is working on my end also. Although, my code is not exactly the same. I am not saving the Image returned by the encode(). However, I am converting the BitMatrix to BitMap instance.
  • Khushboo
    Khushboo about 12 years
    That's true. while I was using "UTF-8", my application was working on some sanners of android and Iphone but not all. When I changed this encoding to "ISO-8859-1", all the scanners/decoders were able to scan the encoded QR Image.
  • Khushboo
    Khushboo about 12 years
    Shaybc, your encoded QR Image might be scannable by some apps of android or Iphone but not all. You need to use "ISO-8859-1" instead of "UTF-8" for successful QR Image encoding. You can test your encoded Qr Image by using Red Laser App or QR Droid app from Google Play.
  • GetUsername
    GetUsername over 10 years
    Just checked both Red Laser and QR Droid and both of them succeed to read UTF8 encoded data
  • truthadjustr
    truthadjustr almost 4 years
    This answer is partially correct, but it is in the wrong context. As this was asked 10 years ago, at that time zxing did not yet implemented the ResultMetadataType.BYTE_SEGMENTS. So at that time, the only way to extract the raw content with no heuristics applied, is to compute the number of bits in the length field (please see Wikipedia). Using the calculated length field, the bits following it are the raw content that got shifted by 4 bits. To compute the length field, you have to do the same exact algo in how the QR code allocated the size based on ECC and the data size.
  • truthadjustr
    truthadjustr almost 4 years
    This answer is not related to the question.
  • truthadjustr
    truthadjustr almost 4 years
    This is the most appropriate reference answer.