The DelphiZXingQRCode unit, ported from ZXing by Debenu, allows to easily add QR Code generation to your Delphi VCL applications.
While the DelphiZXingQRCode was designed to support VCL applications (and be used in newer as well as in older Delphi versions), it can also easily be “upgraded” for Fire Monkey applications – be it desktop or mobile (Android, iOS).
DelphiZXingQRCode -> FireMonkey Support
To FireMonkey-enable the DelphiZXingQRCode unit a few changes to the source code were required.
The unit uses “contnrs” (System.Contnrs.pas) unit which implements TObjectList (used by the unit) and other container like classes. Under FireMonkey for mobile, more specifically under ARC, the classes contained in System.Contnrs.pas cannot be used because they are not ARC compliant.
Equivalent classes are to be found in System.Generics.Collections.pas (ARC compliant) where generics versions of TObjectList is defined.
Therefore, the first change is to replace “uses contnrs, …” with “uses System.Generics.Collections, …”
This also requires to make changes like: from “Blocks: TObjectList;” to “Blocks: TObjectList<TBlockPair>;” – that is to use strongly typed generics list classes.
Further, there are lots of “ansistring” and “widestring” types used for parameters in various functions inside the unit. If you want to go mobile, those are not supported and you should use “string”. More info here: Migrating Delphi Code to Mobile from Desktop.
Therefore, the second change to the unit would be to replace “widestring” with “string” and “ansistring” with “array of byte” (or something else as explained in the article).
Single Pixel Drawing (and Other Canvas Drawing) FireMonkey Style
In the VCL, the TCanvas class allows accessing single pixels through the “Canvas.Pixels()” property. In FireMonkey this is not supported and you have to use the SetPixel property of a TBitmapData instance.
Once the qr code is generated, to convert it to a bitmap image, in FireMonkey:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
var
QRCode: TDelphiZXingQRCode;
Row, Column:
Integer
;
pixelColor : TAlphaColor;
vBitMapData : TBitmapData;
rSrc, rDest : TRectF;
s :
widestring
;
begin
QRCode := TDelphiZXingQRCode
.
Create;
try
QRCode
.
Data := edtText
.
Text;
QRCode
.
Encoding := TQRCodeEncoding(cmbEncoding
.
ItemIndex);
QRCode
.
QuietZone := StrToIntDef(edtQuietZone
.
Text,
4
);
QRCodeBitmap
.
SetSize(QRCode
.
Rows, QRCode
.
Columns);
for
Row :=
0
to
QRCode
.
Rows -
1
do
begin
for
Column :=
0
to
QRCode
.
Columns -
1
do
begin
if
(QRCode
.
IsBlack[Row, Column])
then
pixelColor := TAlphaColors
.
Black
else
pixelColor := TAlphaColors
.
White;
if
QRCodeBitmap
.
Map(TMapAccess
.
maWrite, vBitMapData)
then
try
vBitMapData
.
SetPixel(Column, Row, pixelColor);
finally
QRCodeBitmap
.
Unmap(vBitMapData);
end
;
end
;
end
;
finally
QRCode
.
Free;
end
;
//refresh image control imgQRCode is a TImage
{code below}
end
;
|
Note: compare this with the VCL approach of TDelphiZXingQRCode usage.
VCL’s Canvas.StretchDraw to FMX’s Canvas.DrawBitmap
There’s no StrecthDraw in FMX’s Canvas class. There’s DrawBitmap. StretchDraw by default does no antialiasing. FMX does. So I had to play a little until I was able to make it work correctly.
I’ve decided to use TImage and not TPaintBox as I was simply not able to make TPictureBox in FireMonkey draw what I wanted
Using TImage, the code to get/display the generated QR Code is as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//refresh image. imgQRCode is a TImage
imgQRCode
.
Bitmap
.
SetSize(QRCodeBitmap
.
Width, QRCodeBitmap
.
Height);
rSrc := TRectF
.
Create(
0
,
0
, QRCodeBitmap
.
Width, QRCodeBitmap
.
Height);
rDest := TRectF
.
Create(
0
,
0
, imgQRCode
.
Bitmap
.
Width, imgQRCode
.
Bitmap
.
Height);
if
imgQRCode
.
Bitmap
.
Canvas
.
BeginScene
then
try
imgQRCode
.
Bitmap
.
Canvas
.
Clear(TAlphaColors
.
White);
imgQRCode
.
Bitmap
.
Canvas
.
DrawBitmap(QRCodeBitmap, rSrc, rDest,
1
);
finally
imgQRCode
.
Bitmap
.
Canvas
.
EndScene;
end
;
|
Aliasing – Do Not Need It!
By default, in FMX, when using DrawBitmap to resize it, antialiasing is used by default. There are two properties you need to set to ensure a bigger copy of your (small) qr code is drawn pixel-copy-perfect.
1
2
3
4
|
begin
imgQRCode
.
DisableInterpolation :=
true
;
imgQRCode
.
WrapMode := TImageWrapMode
.
iwStretch;
end
;
|
In a FireMonkey Desktop application, this works as expected.
On FireMonkey Mobile it does not – the resized image is still blurred (antialiasing used).
That’s it. QR Codes generation in FireMonkey – both desktop and mobile!
Download the FMX version of DelphiZXIngQRCode.pas: FMX-DelphiZXIngQRCode
Comments are welcome (even more if you know how to resize a bitmap in FireMonkey on mobile without antialiasing)!
There are two major problems with that approach:
Firstly, using a Map-Operation in the inner loop, which will be executed for each pixel. Since Firemonkey (ab)uses Textures for Bitmap Canvas, very costly copy operations will take place. And not only the pixel to be changed, but the whole image is copied back and forth.
Would be better trying with TSurfaceBitmap instead.
And second, the mentioned “forced” antialiasing which causes images to look ugly on mobile, which could also lead to the resulting QRCode becoming unreadable eventually.
Try the following, not tested on mobile but I believe it will work:
procedure
TForm1
.
Update;
const
downsizeQuality:
Integer
=
2
;
// bigger value, better quality, slower rendering
var
QRCode: TDelphiZXingQRCode;
Row, Column:
Integer
;
pixelColor : TAlphaColor;
vBitMapData : TBitmapData;
pixelCount, y, x:
Integer
;
columnPixel, rowPixel:
Integer
;
function
GetPixelCount(AWidth, AHeight:
Single
):
Integer
;
begin
if
QRCode
.
Rows >
0
then
Result := Trunc(Min(AWidth, AHeight))
div
QRCode
.
Rows
else
Result :=
0
;
end
;
begin
QRCode := TDelphiZXingQRCode
.
Create;
try
QRCode
.
Data := edtText
.
Text;
QRCode
.
Encoding := TQRCodeEncoding(cmbEncoding
.
ItemIndex);
QRCode
.
QuietZone := StrToIntDef(edtQuietZone
.
Text,
4
);
pixelCount := GetPixelCount(imgQRCode
.
Width, imgQRCode
.
Height);
case
imgQRCode
.
WrapMode
of
TImageWrapMode
.
iwOriginal,TImageWrapMode
.
iwTile,TImageWrapMode
.
iwCenter:
begin
if
pixelCount >
0
then
imgQRCode
.
Bitmap
.
SetSize(QRCode
.
Columns * pixelCount,
QRCode
.
Rows * pixelCount);
end
;
TImageWrapMode
.
iwFit:
begin
if
pixelCount >
0
then
begin
imgQRCode
.
Bitmap
.
SetSize(QRCode
.
Columns * pixelCount * downsizeQuality,
QRCode
.
Rows * pixelCount * downsizeQuality);
pixelCount := pixelCount * downsizeQuality;
end
;
end
;
TImageWrapMode
.
iwStretch:
raise
Exception
.
Create(
'Not a good idea to stretch the QR Code'
);
end
;
if
imgQRCode
.
Bitmap
.
Canvas
.
BeginScene
then
begin
try
imgQRCode
.
Bitmap
.
Canvas
.
Clear(TAlphaColors
.
White);
if
pixelCount >
0
then
begin
if
imgQRCode
.
Bitmap
.
Map(TMapAccess
.
maWrite, vBitMapData)
then
begin
try
for
Row :=
0
to
QRCode
.
Rows -
1
do
begin
for
Column :=
0
to
QRCode
.
Columns -
1
do
begin
if
(QRCode
.
IsBlack[Row, Column])
then
pixelColor := TAlphaColors
.
Black
else
pixelColor := TAlphaColors
.
White;
columnPixel := Column * pixelCount;
rowPixel := Row * pixelCount;
for
x :=
0
to
pixelCount -
1
do
for
y :=
0
to
pixelCount -
1
do
vBitMapData
.
SetPixel(columnPixel + x,
rowPixel + y, pixelColor);
end
;
end
;
finally
imgQRCode
.
Bitmap
.
Unmap(vBitMapData);
end
;
end
;
end
;
finally
imgQRCode
.
Bitmap
.
Canvas
.
EndScene;
end
;
end
;
finally
QRCode
.
Free;
end
;
end
;
Congratulations for the article, I needed that.
“>” is greater than in my code.
fixed, thanks for noticing.
Hello, If I change AnsiString for UTF8Version variable then i cannot set to print greek letters see bellow procedure TEncoder.Append8BitBytes(const Content: String; Bits: TBitArray; EncodeOptions: Integer); var Bytes: TByteArray; I: Integer; UTF8Version: AnsiString; Is there any suggestion? thanks
Pingback: Generate QR Codes Using Firemonkey In Dephi XE5 On IOS And Android | Delphi Firemonkey, Delphi Android, Delphi IOS
Hi, Ive got some following trouble:
[DCC Error] DelphiZXIngQRCode.pas(242): E2029 Identifier expected but ‘ARRAY’ found [DCC Error] DelphiZXIngQRCode.pas(854): E2029 Identifier expected but ‘ARRAY’ found [DCC Error] DelphiZXIngQRCode.pas(860): E2010 Incompatible types: ‘Byte’ and ‘string’ [DCC Error] DelphiZXIngQRCode.pas(870): E2008 Incompatible types [DCC Error] DelphiZXIngQRCode.pas(1632): E2010 Incompatible types: ‘Dynamic array’ and ‘AnsiString’ [DCC Error] DelphiZXIngQRCode.pas(1642): E2010 Incompatible types: ‘Dynamic array’ and ‘AnsiString’ [DCC Error] DelphiZXIngQRCode.pas(3018): E2010 Incompatible types: ‘TBlockPair’ and ‘TGenericGFPoly’ [DCC Error] DelphiZXIngQRCode.pas(3038): E2010 Incompatible types: ‘TBlockPair’ and ‘TGenericGFPoly’
Let me know, what could I do. Thanks.
Like: FireMonkey or VCL application?
it is XE5 native android.
@Luke: I’ve sent the modified pas file to your email…
wohooo…thanks a lot, man!