Howto: QR Codes on receipts with escpos-php

ESC/POS is a binary protocol for speaking to receipt printers. It contains a command for printing QR Codes on compatible printers. The PHP library escpos-php is used for generating these commands in PHP. This post will show you how to use it to generate QR codes on your receipt printer.

For printers which don’t support this command, a second option is available: sending the QR code as an image.

Getting started

First up, you need your receipt printer to be working with escpos-php. Here are some resources about how to go about that:

Option 1: Direct printing

This method sends QR codes directly. From the documentation, the syntax for this command is:

qrCode($content, $ec, $size, $model)

Print the given data as a QR code on the printer.

  • string $content: The content of the code. Numeric data will be more efficiently compacted.
  • int $ec Error-correction level to use. One of Printer::QR_ECLEVEL_L (default), Printer::QR_ECLEVEL_M, Printer::QR_ECLEVEL_Q or Printer::QR_ECLEVEL_H. Higher error correction results in a less compact code.
  • int $size: Pixel size to use. Must be 1-16 (default 3)
  • int $model: QR code model to use. Must be one of Printer::QR_MODEL_1, Printer::QR_MODEL_2 (default) or Printer::QR_MICRO (not supported by all printers).

The below code snippets are directly from the QR code printing demo, showing how the output changes with the options given.

Simple example

This is the simplest use, with all default options. QR codes can be aligned in the same way as text or images on the page:

2015-04-escposqr-01-demo
// Most simple example
title($printer, "QR code demo\n");
$testStr = "Testing 123";
$printer -> qrCode($testStr);
$printer -> text("Most simple example\n");
$printer -> feed();

// Demo that alignment is the same as text
$printer -> setJustification(Printer::JUSTIFY_CENTER);
$printer -> qrCode($testStr);
$printer -> text("Same example, centred\n");
$printer -> setJustification();
$printer -> feed();

Data encoding

This is a demonstration of saving different types of data in a code. Numeric data is packed more efficiently than text. Binary data can also be stored.

2015-04-escposqr-02-dataencoding
// Demo of numeric data being packed more densly
title($printer, "Data encoding\n");
$test = array(
	"Numeric"      => "0123456789012345678901234567890123456789",
	"Alphanumeric" => "abcdefghijklmnopqrstuvwxyzabcdefghijklmn",
	"Binary"       => str_repeat("\0", 40));
foreach($test as $type => $data) {
	$printer -> qrCode($data);
	$printer -> text("$type\n");
	$printer -> feed();
}

Error correction levels

QR codes support fout levels of error correction. More error correction results in larger, but more durable codes:

2015-04-escposqr-03-errorcorrection
// Demo of error correction
title($printer, "Error correction\n");
$ec = array(
	Printer::QR_ECLEVEL_L => "L",
	Printer::QR_ECLEVEL_M => "M",
	Printer::QR_ECLEVEL_Q => "Q",
	Printer::QR_ECLEVEL_H => "H");
foreach($ec as $level => $name) {
	$printer -> qrCode($testStr, $level);
	$printer -> text("Error correction $name\n");
	$printer -> feed();
}

Code size

The defauly codes are quite small. Each pixel can be blown up, up to 16x, using the size option:

2015-04-escposqr-04-pizelsize
// Change size
title($printer, "Pixel size\n");
$sizes = array(
	1 => "(minimum)",
	2 => "",
	3 => "(default)",
	4 => "",
	5 => "",
	10 => "",
	16 => "(maximum)");
foreach($sizes as $size => $label) {
	$printer -> qrCode($testStr, Printer::QR_ECLEVEL_L, $size);
	$printer -> text("Pixel size $size $label\n");
	$printer -> feed();
}

QR models

QR models have different appearances, storage parameters and physical sizes. The default (Model 2) is most common. The printer used here does not support micro QR codes, and used Model 2 as a fallback.

2015-04-escposqr-05-qrmodel
// Change model
title($printer, "QR model\n");
$models = array(
	Printer::QR_MODEL_1 => "QR Model 1",
	Printer::QR_MODEL_2 => "QR Model 2 (default)",
	Printer::QR_MICRO => "Micro QR code\n(not supported on all printers)");
foreach($models as $model => $name) {
	$printer -> qrCode($testStr, Printer::QR_ECLEVEL_L, 3, $model);
	$printer -> text("$name\n");
	$printer -> feed();
}

Note

To run the snippets, you need to initialise the printer, and define a title() function to print headings, like so:

<?php
/* Demonstration of available options on the qrCode() command */
require __DIR__ . '/autoload.php';
use Mike42\Escpos\Printer;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
$connector = new FilePrintConnector("/dev/usb/lp0");
$printer = new Printer($connector);

// ....

// Cut & close
$printer -> cut();
$printer -> close();

function title(Escpos $printer, $str) {
	$printer -> selectPrintMode(Printer::MODE_DOUBLE_HEIGHT | Printer::MODE_DOUBLE_WIDTH);
	$printer -> text($str);
	$printer -> selectPrintMode();
}

Option 2: Printing codes as images

Not all printers can generate QR codes natively. The work-around is to generate a QR code as an image on the computer, and then send that image to the printer. This is slightly slower, so if you print a lot of codes, you should consider upgrading your printer.

First up, fetch a copy of phpqrcode and generate some codes. I wont attempt to document the whole library here, but in short, it supports most of the same features as the native QR command. To generate a code, you simply use QRcode::png:

require_once("phpqrcode/qrlib.php");
QRcode::png("testing123", "test.png", 'L', 10, 0);

To print a PNG image, use the bitImage() command (the graphics command is also only available on newer printers):

require __DIR__ . '/autoload.php';
use Mike42\Escpos\EscposImage;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
$img = EscposImage::load("test.png"); // Load image
$connector = new FilePrintConnector("/dev/usb/lp0"); // Add connector to your printer here
$printer = new Printer($connector);
$printer -> bitImage($img);
$printer -> feed();
$printer -> text("Code printed from image\n");

$printer -> cut();
$printer -> close();

A more sophisticated way to hack in phpqrcode would be to add this new code as a different implementaton of the qrCode function. Other improvements are:

  • Use temporary files to avoid concurrency issues:
  • Where possible, expand the code on the printer, to send less data
<?php
require __DIR__ . '/autoload.php';
use Mike42\Escpos\Printer;

require_once("phpqrcode/qrlib.php");

class EscposQrImgPrinter extends Printer {
	function qrCode($content, $ec = self::QR_ECLEVEL_L, $size = 3, $model = self::QR_MODEL_2) {
		// Validate inputs
		self::validateString($content, __FUNCTION__);
		self::validateInteger($ec, 0, 3, __FUNCTION__);
		self::validateInteger($size, 1, 16, __FUNCTION__);
		$model = self::QR_MODEL_2; // Only Model 2 supported in phpqrcode, change back to it.
		$sizeMod = 0;
		if($size % 2 == 0) { // Optimisation to enlarge codes on the priner, sending 1/4 of the data.
			$size /= 2;
			$sizeMod = self::IMG_DOUBLE_HEIGHT | self::IMG_DOUBLE_WIDTH;
		}
		// Map error-correction to phpqrcode levels
		$ecMap = array(QR_ECLEVEL_L => 'L',
			QR_ECLEVEL_M => 'M',
			QR_ECLEVEL_Q => 'Q',
			QR_ECLEVEL_H => 'H');
		// Create QR code in temp file, and print it.
		$tmpfname = tempnam(sys_get_temp_dir(), "escpos-php");
		QRcode::png("testing123", $tmpfname, $ecMap[$ec], $size, 0);
		$img = new EscposImage($tmpfname);
		$this -> bitImage($img, $sizeMod);
		unlink($tmpfname);
	}
}

This new class uses phpqrcode in the background instead, and can be accessed with the same function calls as the parent class:


require __DIR__ . '/autoload.php';
use Mike42\Escpos\Printer;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
$connector = new FilePrintConnector("/dev/usb/lp0");
$printer = new EscposQrImgPrinter($connector);

$testStr = "Testing 123";
$printer -> qrCode($testStr);
$printer -> text("Most simple example\n");
$printer -> feed();
$printer -> cut();

$printer -> close();

The only visible difference between the implementations is a few pixels of spacing below the image.

Beautiful QR Codes

The verdict is in. QR codes are ugly. But they don’t have to be. Check out the modified code featured on this Wikipedia article. You don’t need to be a QR Code expert to do something like that.

The basic idea is that QR codes have error Correction. We can generate codes which store the data in multiple places, so that scanners will still read them if they are damaged.

Scripting the whole operation

Tedious image editing is not my cup of tea, so I made a PHP class to apply some templates to QR codes and add a centred logo for us.

Note: If you don’t have ImageMagick for PHP, read this page. On Ubuntu, apt-get install php5-imagick worked fine for me.

This is the basic formula:

QR Code + Template + Logo = Pretty PR Code

First we need the QR code. It is best generated with the phpqrcode library. Adding the logo wont work unless we use high error correction (H):

QRcode::png("http://bitrevision.com", "code.png", 'H', 8, 0);

After that, code.png looks like this:

Now the templates. Note that we used 8×8 pixels per block and 0 for the border above. The resulting code may line up with one of these templates, adding white lines over the image. Save these images to a ‘template’ folder:

Next, the logo. We have one of those:

Time for the code. This class will do most of the work. Just check the template folder contains an overlay that fits your code.

class QR_Pretty {
	public $template_base = "template/template-{SIZE}.png";
	public $qr = false;
	public $geometry = false;

	function prettify($file, $logo, $output = '') {
		/* Load image */
		$this -> qr = new Imagick();
		$this -> qr -> readImage($file);
		$this -> geometry = $this -> qr -> getImageGeometry();

		/* Perform modifications */
		$this -> add_template();
		$this -> add_logo($logo);

		/* Output image */
		if($output != '') {
			$this -> qr -> setImageFileName($output);
		}
		$this -> qr -> writeImage();
	}

	private function add_template() {
		/* This will overlay a template containing white lines,
			to make the QR codes look less code-ful */
		$size = $this -> geometry['width'];
		$template_filename = str_replace("{SIZE}", $size, $this -> template_base);
		$template = new Imagick();
		$template -> readImage($template_filename);
		$this -> qr -> compositeImage($template, imagick::COMPOSITE_OVER, 0, 0 );
	}

	private function add_logo($logo_file = false) {
		/* This places a logo in the middle of the QR code,
			64x64 would be advisable :) */
		if(!$logo_file) {
			/* No logo to add */
			return false;
		}
		$logo = new Imagick();
		$logo -> readImage($logo_file);
		$logo_size = $logo -> getImageGeometry();
		$x = ($this -> geometry['width'] - $logo_size['width']) / 2;
		$y = ($this -> geometry['height'] - $logo_size['height']) / 2;
		$this -> qr -> compositeImage($logo, imagick::COMPOSITE_OVER, $x, $y);
	}
}

To use the above class is quite simple. Once you have code.png, logo.png, and a folder full of templates, just do this:

$qr = new QR_Pretty();
$qr -> prettify("code.png", "bitrevis.png", "pretty.png");

That gives us pretty.png, which looks like this:

Thanks to that error correction, this picture still scans and takes us to http://bitrevision.com. Slightly larger logos can be used, but 64×64 looks good and scans reliably. Try embedding logos with transparency too!

This means no more excuses for ugly QR codes. Integrate this into your scripts right away.