# How to generate image like QR code with Mathematica

How to generate such form of QR code which displays an image itself but is still scan-able?

• In case anyone is slightly interested this is possible because QR codes can support high data redundancy, thus adding an image to a QR code, e.g. unitag.io/qrcode, for marketing purposes is useful. – Daniel S. Fowler Jul 5 '16 at 13:03
• yes, I know one can add an image in the center of a QRcode. It's just this version can make the image full sized~ – Wjx Jul 5 '16 at 13:12
• An alternate approach that does not rely on abusing the pixel center reading is Russ Cox's QArt codes – Nayuki Jul 5 '16 at 19:45
• that's great and would generate a easier recognizable result~ It's just I don't know how to code a QRcode from the beginning~ Maybe you can post another answer if you want~ :) – Wjx Jul 6 '16 at 2:18

This version of generation code is originally posted at wolfram community.

I guess a story-telling type post would attract more upvotes and probably give some insight about how to 'solve problems' using Mathematica, so I would go into details and try to explain not only the code but also how I figured out how to write them.

# How this works?

In fact, this form of QR code is not my original idea, I came across such type of QR code on internet but failed to find its origin. So I tried to figure out the principle by myself.

The marker on the corner are three times coarser than the majority of the QR. So I initially hypothesize that the QR recognition algorithm would first average the brightness of a segment, turning it into a normal QR and then recognize it. The code I used is as follows:

Block[{img = Import["http://community.wolfram.com//c/portal/getImageAttachment?filename=mathematica1.png&userId=1340903"], dat, partitioned},
partitioned = Partition[dat, {3, 3}];
Grid[{ImageResize[#, Dimensions@dat], BarcodeRecognize@#} & /@
{Image@dat,Binarize@Image@Map[Mean@*Flatten, partitioned, {2}]}]
]


The result proved me wrong as the averaged version cannot be properly recognized. Then further observer the QR code, I found that there are mysterious dots even in the places which should be purely white, also the dots are a bit too structural. So I suspect that normal QR code recognition algorithm only takes the color of the center dot, so I added this to the previous code:

Map[#[[2,2]]&,partitioned,{2}]


then it worked out properly!

As we've already cracked the theory, we can now generate some of our own.

# How to generate?

QR code generation

First we can use BarcodeImage to generate a QR code, for example here I would use: "This is a sample QR generated by Mathematica!" as the content of the QR code:

text = "This is a sample QR generated by Mathematica!";
qrraw = BarcodeImage[text, {"QR", "H"}, 1]
BarcodeRecognize@qrraw

Image processing

Then we create a black and white image to use as background, for example here we use the wolfram wolf icon:

Import, convert to grayscale and adjust the grayscale a bit:

img=ColorConvert[Rasterize[Graphics[{
Inset[Import["http://community.wolfram.com//c/portal/getImageAttachment?filename=wolframwolf.png&userId=1340903"],{.6,.4},Automatic,.8],
Text[Style["WOLFRAM",Bold,14],{.5,.92}]
},PlotRange->{{0,1},{0,1}},ImageSize->3ImageDimensions@qrraw]],"Grayscale"]^.45


which returns:

Note that in order to get enough resolution while keeping the QR code easy to scan, the dimension of the QR code is best in the range of [25,50], one can test that using ImageDimensions@img and adjust it by changing the error correction level by setting {"QR",lev} where lev can be "L", "M", "Q", or "H".

Merging

Then we should merge this two image together. Here we use the technique of dither to display grayscale image using only white and black pixels. In the process of dithering, we should notice that at center of each 9*9 pixel the value should correspond to the value in the QR image, or the QR code would be invalid. The code could be easily written out as follows:

dithering[imgdat_, qrdat_] :=
Block[{imgdat1 = imgdat, dimx, dimy, tmp1, tmp2, f = UnitStep[# - .5] &},
{dimx, dimy} = Dimensions@imgdat;
Quiet@Do[
(*Rounding*)
tmp1 = If[Mod[{i, j}, 3] == {2, 2}, qrdat[[(i + 1)/3, (j + 1)/3]], f[imgdat1[[i, j]]]];
tmp2 = Clip[imgdat1[[i, j]] - tmp1, {-.5, .5}];
(*Diffuse Error*)
imgdat1[[i, j]] = tmp1;
imgdat1[[i, j + 1]] += 0.4375 tmp2;
If[j != 1, imgdat1[[i + 1, j - 1]] += 0.1875 tmp2];
imgdat1[[i + 1, j]] += 0.3125 tmp2;
imgdat1[[i + 1, j + 1]] += 0.0625 tmp2
, {i, dimx}, {j, dimy}];
imgdat1
]


Special attention should be paid to the handling key pixels of the QR code, the error created by introducing it should not be ignored, but its influence should be limited in a range, so here a Clip in error is required, while in a traditional dithering process it would be redundant.

Apply dithering to the image and we have:

Image[ditherdat=dithering[ImageData@img, ImageData@qrraw]]


Refinement

One can see that the shape of the original image is quite well preserved and key points of the QR code are properly dealt with. Then the final step is to process the key features on the corner and edge of the QR code, which is quite trivial:

replicate = (Flatten[ConstantArray[#, {3, 3}], {{3, 1}, {4, 2}}] &);

refineqr[qrdat_] :=
Block[{qrd = qrdat, d = Length[qrdat]},
(*Corner*)
(qrd[[#1 ;; 24 #1 ;; #1, #2 ;; 24 #2 ;; #2]] = replicate[qrd[[2 #1 ;; 23 #1 ;; 3 #1, 2 #2 ;; 23 #2 ;; 3 #2]]]) & @@@ {{1, 1}, {1, -1}, {-1, 1}};
(*Edge*)
qrd[[22 ;; d - 21, 19 ;; 21]] = Transpose[qrd[[19 ;; 21, 22 ;; d - 21]] = replicate[{Mod[Range[(d + 1)/3 - 14], 2]}]];
qrd]


Then apply this to previously get result, we get the final result, which is scan-able:

Image[final = refineqr@ditherdat]
BarcodeRecognize@%


It's usually favourable to have a 3x zoom to the image:

Image@replicate@final


A fully packed version is shown in the attachment notebook file, where:

createqr[text,img]


would generate the same result.

Further optimizations could include using machine learning to further refine the display effect. Sharper lines, Less interfering key points and more could be expected.

ENJOY~

# Update

@Henrik Schachner kindly remind me that the previous QR is not that easy to scan with average QR scanning software. So I made some tiny updates to make the QR more standardized and much more easier to scan:

refineqr[qrdat_] :=
Block[{qrd = qrdat, d = Length[qrdat], temp = Fold[ArrayPad[#1, 1, #2] &, {{{0}}, 1, 0}], p},
p = Position[Round@ListCorrelate[temp, qrdat[[2 ;; ;; 3, 2 ;; ;; 3]], {{1, 1}, {-1, -1}}, 0, Abs@*Subtract], 0, 2];
(*Corner*)
(qrd[[#1 ;; 24 #1 ;; #1, #2 ;; 24 #2 ;; #2]] = replicate[qrd[[2 #1 ;; 23 #1 ;; 3 #1, 2 #2 ;; 23 #2 ;; 3 #2]]]) & @@@ {{1, 1}, {1, -1}, {-1, 1}};
(*Edge*)
qrd[[22 ;; d - 21, 19 ;; 21]] = Transpose[ qrd[[19 ;; 21, 22 ;; d - 21]] = replicate[{Mod[Range[(d + 1)/3 - 14], 2]}]];
(*Special*)
(qrd[[3 #1 - 2 ;; 3 #1 + 12, 3 #2 - 2 ;; 3 #2 + 12]] = replicate@temp) & @@@ p;
qrd]


after this update, the QR code would look like this:

After minor manual edition, it could be like:

Maybe this would be easier to scan due to the newly add correction block on the right-down corner.

Also, I think I found a better realization using same basic design principle here.

# Original Post

The original QRCode is a single piece (I don't dare put it here as it's actually a figure of Chinese pre-president ......)

The most interesting phenomenon is that the reference bar has different pixel size as others. So I wrote a brief mathematica code and find out the following:

QRCode Reader will only check the color of center points defined by the reference bar and IGNORE all other points!!!

Thus we can use pixels other than the center point to create color or graylevel effect and keep the center pixel untouched to carry the information we need.

The code is fully automated and runs at quite an acceptable speed (About a second)

The code is here:

text = "This is an interesting yet beautiful QRCode that consists of an image~";
(*text="http://www.rdfz.cn/"*)

img = ImageData@
ColorConvert[BarcodeImage[text, {"QR", "H"}, 50], "LAB"];
d = Length@img;
color[Lmat_, A_, B_] := Map[{.8 # + .2, A, B} &, Lmat, {2}]
f[0, {_?(# >= .9 &), _, _}] := color[1 - DiskMatrix[0, 3], 0, 0]
f[1, {_?(# < .1 &), _, _}] := color[DiskMatrix[0, 3], 0, 0]
f[x_, {L_, A_, B_}] :=
color[Partition[
Insert[RandomSample[1 - UnitStep[Range@8 - Floor[L*10] + x]], x,
5], 3], A, B]
f[x_List, LAB_List] := f @@@ Thread@{x, LAB}
cg[m_] := ArrayFlatten@Map[ConstantArray[#, {3, 3}] &, m, {2}]

dat = ArrayFlatten[
f[img[[;; , ;; , 1]],
ImageData[ColorConvert[tgi, "LAB"]][[;; , ;; , ;; 3]]]];
(dat[[Span[24 #1], Span[24 #2]]] =
cg[img[[Span[8 #1], Span[8 #2]]]]) & @@@ {{1, 1}, {1, -1}, {-1,
1}};
dat[[22 ;; 3 d - 21, 19 ;; 21]] =
Transpose[
dat[[19 ;; 21, 22 ;; 3 d - 21]] =
cg[{{#, 0, 0} & /@ Mod[Range[d - 14], 2]}]];

Blur[Image[dat, ColorSpace -> "LAB", ImageSize -> Large], 1]
ColorConvert[%, "Grayscale"]


You need to insert the text and the original image.