LinkedinGitHub
Close

Generating barcodes in Unity with ZXIng

So recently I had to implement a barcode generator in Unity. What I needed was:

  1. The ability to generate different types of barcodes (QR, Code 128, PDF 417 etc…)
  2. Get a Texture2D / Sprite from a piece of data that needed to be encoded.
  3. To do it efficiently and consistently

A few google searches later I found that the vastly popular ZXIng library has a .NET port, which offers a Unity optimized DLL, so I decided to try it out.

Your first barcode

Naturally, step one is to try an example.

First, let’s get some boilerplate out of the way, we’ll be focusing on the GenerateBarcode method:

using UnityEngine;
using UnityEngine.UI;
using ZXing;
using ZXing.Common;

[RequireComponent(typeof(RawImage))]
public class BarcodeGenerator : MonoBehaviour
{
    [SerializeField] private BarcodeFormat format = BarcodeFormat.QR_CODE;
    [SerializeField] private string data = "test";
    [SerializeField] private int width = 512;
    [SerializeField] private int height = 512;

    private RawImage cRawImage;

    private void Start()
    {
        cRawImage = GetComponent();

        // Generate the texture
        Texture2D tex = GenerateBarcode(data, format, width, height);

        // Setup the RawImage
        cRawImage.texture = tex;
        cRawImage.rectTransform.sizeDelta = new Vector2(tex.width, tex.height);
    }

    private Texture2D GenerateBarcode(string data, BarcodeFormat format, int width, int height)
    {
        // TODO: Implement me
        return null;
    }
}

Then it’s time to start with the meaty part of our generator. For the first example I used something sourced from somewhere on the web:

private Texture2D GenerateBarcode(string data, BarcodeFormat format, int width, int height)
{
    BarcodeWriter writer = new BarcodeWriter
    {
        Format = BarcodeFormat.QR_CODE,
        Options = new EncodingOptions
        {
            Height = height,
            Width = width
        }
    };

    Color32[] pixels = writer.Write("data to encode");

    Texture2D tex = new Texture2D(width, height);
    tex.SetPixels32(pixels);
    tex.Apply();

    return tex;
}

This gives a pretty nice result:
qr

What about if we try to scale it up, to say, 512*512? Well then we get this:
qr2
ಠ_ಠ

A few (a lot) of test later it still wasn’t working. 256×256 was ok for most cases, but once i tried to scale it up the pixel array gets messed up and nothing worked anymore.

The Solution

After some more tests and messing around, I found the solution. The issue is that the width / height you supply to the BarcodeWriter is only a reference it uses, and it doesn’t mean that that’s the size of the output pixel array. It also looks like their Color32Renderer has a bug that incorrectly renders the color pixel array (or I’m just using it wrong, who knows).

What I ended up doing was going with a different approach. Instead of using BarcodeWriter to generate the pixel array directly, I used MultiCodeWriter to generate the BitMatrix, and then generate the pixel array manually:

private Texture2D GenerateBarcode(string data, BarcodeFormat format, int width, int height)
{
    // Generate the BitMatrix
    BitMatrix bitMatrix = new MultiFormatWriter()
        .encode(data, format, width, height);

    // Generate the pixel array
    Color[] pixels = new Color[bitMatrix.Width * bitMatrix.Height];
    int pos = 0;
    for (int y = 0; y < bitMatrix.Height; y++)
    {
        for (int x = 0; x < bitMatrix.Width; x++)
        {
            pixels[pos++] = bitMatrix[x, y] ? Color.black : Color.white;
        }
    }

    // Setup the texture
    Texture2D tex = new Texture2D(bitMatrix.Width, bitMatrix.Height);
    tex.SetPixels(pixels);
    tex.Apply();

    return tex;
}

With this approach, all codes that can be generated by ZXIng (that have encoders available) will be properly generated. Tested with:

  • QR
  • PDF 417
  • Code 128
  • AZTEC
  • CODABAR
  • Code 93
  • Code 39
  • Data Matrix
  • EAN-8
  • EAN-13
  • ITF
  • UPC-A
  • UPC-E
  • MSI
  • PLESSEY

Resources

You can get the ZXing.Net library here: https://github.com/micjahn/ZXing.Net/releases

The release zip (0.16.2 at the time of writing) contains a unity folder with the DLL. Just pop that into your project and you’re good to go.

You can also check out the full source used in this blog on GitHub.

Extra bits

If you’re picky like me, you can also make the process threaded. In my case, I used UniRx (note that setting the pixels to the texture has to be done on main thread, hence the ObserveOnMainThread):

private IObservable GenerateBarcode(string data, BarcodeFormat format, int width, int height)
{
    return Observable.Start(() =>
                     {
                         // Generate the BitMatrix
                         BitMatrix bitMatrix = new MultiFormatWriter()
                             .encode(data, format, width, height);

                         // Generate the pixel array
                         Color[] pixels = new Color[bitMatrix.Width * bitMatrix.Height];
                         int pos = 0;
                         for (var y = 0; y < bitMatrix.Height; y++)
                         {
                             for (var x = 0; x < bitMatrix.Width; x++)
                             {
                                 pixels[pos++] = bitMatrix[x, y] ? Color.black : Color.white;
                             }
                         }

                         return new Tuple(new[] {bitMatrix.Width, bitMatrix.Height}, pixels);
                     }, Scheduler.ThreadPool)
                     .ObserveOnMainThread() // The texture itself needs to be created on the main thread.
                     .Select(res =>
                     {
                         Texture2D tex = new Texture2D(res.Item1[0], res.Item1[1]);
                         tex.SetPixels(res.Item2);
                         tex.Apply();
                         return tex;
                     });
}

 

If we use the profiler to now measure the spike for generating our image we see a noticable decrease, however a small spike still remains, due to the fact that the texture has to be created on the main thread:

Without threading:
profilerSpikeNoThread

With threading:
profilerSpikeThreaded

You can go further, and completely eliminate the spike by modifying the method described in this post to load from a byte array instead of  a file, and use that to load your texture into memory. However, this was good enough for me, and even without threading the spike should not produce a big drop in framerate.

1 thought on “Generating barcodes in Unity with ZXIng

Leave a Reply

Your email address will not be published. Required fields are marked *