Creating QR Codes with embedded images in PHP

John Rix
4 min readOct 14, 2022

We’ve all seen those fancy QR codes out there where the creator sticks their logo or other image in the middle of the QR code and it still scans successfully.

For a long time, I assumed this was a special feature of the QR code specifications to allow the encoded data to wrap around the image. Well… I was wrong. TL;DR — Turns out, it just boils down to using a high error correction level when creating the code so that some portion of it can be obscured without invalidating it.

I had occasion to go exploring this capability the other day to embed customer logos in QR codes for event booking page URLs for the online bookings platform I operate. We already had the QR codes in place, for which we use a great PHP library named Endroid/qr-code (which as of this writing is still actively maintained).

I had not looked at the library for some time though and was delighted to discover that it more recently added support for embedding logos as part of its API. The documentation on the Github page is a bit thin on the ground though, so I had to go code diving to discover the following builder methods available for this purpose:

public function logoPath(string $logoPath): BuilderInterface;     public function logoResizeToWidth(int $logoResizeToWidth): BuilderInterface;     public function logoResizeToHeight(int $logoResizeToHeight): BuilderInterface;     public function logoPunchoutBackground(bool $logoPunchoutBackground): BuilderInterface;

All looks handy enough. Here’s a sample of generating the QR code with the logo embedded:

use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
use Endroid\QrCode\Writer\PngWriter;
...$url = 'https://example.com';
$logoPath = 'myLogoFile.png';
$qrCode = Builder::create()
->writer(new PngWriter())
->writerOptions([])
->data($url)
->logoPath($logoPath)
->logoResizeToWidth(100)
->encoding(new Encoding('ISO-8859-1'))
// Here's the sneaky bit that makes the QR Code work with the image
->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
->build();
// Either ...
$qrCodeDataUri = $qrCode->getDataUri();
// or ...
$qrCode->saveToFile('myQRCode.png');
// or other output options also available

Not so faaaaasssst…

On experimenting with it though, I found the result is … okay …

First Attempt

… but it really needs a square format logo image with a white background to look at its best. The Endroid library simply rendered the image over the QR code, allowing the code to show through the transparent areas. I tried using the logoPunchoutBackground function…

Second attempt

Really? OK, time to roll up the sleeves and get fancy.

If you just have a single logo that you are working with, that’s easy enough to manipulate it in an image editor to create something that will work here. If like me though, you have lots of different logos of varying aspect ratios, transparencies and resolutions, a better and fully automated solution is needed.

How to cook with gas

Enter claviska/SimpleImage, another handy library for making working with images in PHP nice and easy. With this, we can manipulate the logo image on-the-fly into something that will look great overlayed on the QR code, then feed that directly into the Endroid qr-code builder:

use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
use Endroid\QrCode\Writer\PngWriter;
use claviska\SimpleImage;
...// First, read in the logo file and downscale it to max 100px
$logoImageReader = new SimpleImage();
$logoImageReader
->fromFile($logoPath)
->bestFit(100, 100);
// Next, create a slightly larger image,
// fill it with a rounded white square,
// and overlay the resized logo
$logoImageBuilder = new SimpleImage();
$logoImageBuilder
->fromNew(110, 110)
->roundedRectangle(0, 0, 110, 110, 10, 'white', 'filled')
->overlay($logoImageReader);
// Grab the reformatted logo as a Data URI
// that we can feed into Endroid QR-code
$logoData = $logoImageBuilder->toDataUri('image/png', 100);
$url = 'https://example.com';// Finally, build the QR code and embed the logo
$qrCode = Builder::create()
->writer(new PngWriter())
->writerOptions([])
->data($url)
->logoPath($logoData)
->logoResizeToWidth(100)
->encoding(new Encoding('ISO-8859-1'))
->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
->build()
->saveToFile('myQRCode.png');

Tip: The bestFit() method above downscales the logo but does not result in a 100x100px image, but rather something with max 100px in either dimension, so rectangular images remain rectangular. The SimpleImage library appears to be missing a cavas size adjustment method though, so to work around that, we downscale the logo first, then overlay it onto a separately created SimpleImage image with the right dimensions and the rounded white box already created. The overlay() method will place the source item into the center of the target by default, which is exactly what we want here!

Now, with the text removed from the logo image and just keeping the icon, we get something much more presentable.

The final result

Happy days!

Thanks for reading.

--

--