ZXing.Delphi: a barcode scanning library for Delphi written in pure Object Pascal
ZXing Barcode Scanning object Pascal Library for Delphi VCL and Delphi Firemonkey
At a glance
- What is it?
- ZXing.Delphi is a native Object Pascal port of the ZXing barcode library for Delphi VCL and FireMonkey, with no external binaries to link and a scan API that runs decoders side by side across cores. Version 4 moved it onto the C++ port, which is where the extra symbologies and the speed came from.
- Who is it for?
- Adopt ZXing.Delphi if you are building a scanner in Delphi and want a self-contained native library rather than binding the Java or C++ originals, since the whole point is compiled-in speed with no external dependency. Do not expect it to read every code on the first try, because the error path is a first class feature and a code that is found but unreadable is a normal outcome for damaged or low-contrast labels.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Pascal, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What it is, and why a port exists at all
ZXing.Delphi is a native Object Pascal library that is based on the well known open source Barcode Library: ZXing, Zebra Crossing. It is aimed at all of the FireMonkey platforms and, starting from v3.1, it fully supports also Windows VCL applications.
The reason a port exists is stated in the terms the README uses: you can scan with native compiled speed without the use of linking in external libraries and avoid compatibility issues and dependencies.
That is the whole case. The obvious alternative for a Delphi developer is to bind the Java or C++ ZXing directly, and both routes carry a real cost. A Java binding means shipping a JVM or a JAR and the JNI bridge on a mobile target, and the C++ binding means distributing native libraries per platform and ABI and dealing with the exception and callback model across the language boundary. A pure Object Pascal port compiles into your application, which for FireMonkey means it becomes part of your IPA or your APK with no extra packaging step.
The compatibility range is explicit. It is compatible with Delphi 10.4 Sydney through 13.x Florence, with the note that for XE7 through 10.3 you should use v3.13.1, and it is tested with the latest iOS, Android 32/64, Windows 32/64 and macOS.
The feature list is short and worth reading as a set of commitments. Native compiled barcode scanning for all VCL and FireMonkey platforms, 100% free with no license fees, speed, a simple API, unit tests provided and test projects provided. The unit test directory and the UnitTest/ directory in the repository are what back the last two claims, and the presence of a benchmark/ directory and a boss.json configuration for the build shows there is a performance baseline being tracked.
The project is Apache-2.0 licensed, the primary language is Pascal, the default branch is 4.0, and the last push was on 2026-10-20. The recent releases are v4.0.1 on 2026-10-17, v4.0 on 2026-10-17 and v3.13.1 from 2026-10-02.
What changed in 4.0, and the symbology list it opened up
The README calls version 4.0 by far the biggest step since the port began, and the two things that changed are the engine and the coverage.
The library grew from 10 symbologies to 52. The new ones are named in the release notes and they are the awkward ones: Aztec, PDF417 and MicroPDF417, Micro QR and rMQR, MaxiCode, the GS1 DataBar family and GS1 Composite, DotCode and Han Xin, the stacked codes Codablock F, Code 16K and Code 49, and the postal barcodes from KIX to Intelligent Mail, several of which no other ZXing port reads at all.
That last clause is the interesting one. Most of the additions are formats that have a ZXing implementation somewhere, and the work is porting them. Postal barcodes are the exception, because the upstream Java and C# ports do not cover them, so a Delphi developer using this library can read a set of codes that a Java developer cannot. The port is not strictly behind the reference implementation, it is sometimes ahead of it in a specific direction.
The engine change is the other half. From v4 the library is based on the C++ port, which had lots of better and faster scan strategies. The QR Code and Data Matrix detectors are ports of zxing-cpp, so perspective, curved and dot-peen codes that 3.x missed are read now, and an image pyramid catches the large and blurry ones.
That is a meaningful list of cases. A curved code on a bottle and a dot-peen code on a metal part are exactly the things that fail with a flat detector, and both are common in real scanning work.
The performance claim is specific. On the same images, 4.0 scans about five times faster than 3.x on a single core while trying five times as many formats, and on large camera frames the readers decode side by side on all cores. The luminance conversion, binarizer and 1D readers were rewritten, and the readers run side by side in the tasks of a thread pool, so a frame is decoded on all cores.
The API grew to match. One ScanAsync call scans a camera frame in the background and hands the result to the main thread.
Setting up the scan manager, and the conditional defines older Delphi needs
Before any code, there is one setup step that catches people on older compilers. If you use a Delphi older than 11.1 you need to set a compiler define in your project options under the Delphi compiler conditional defines, and you set it in your target platforms or all platforms.
There are two. FRAMEWORK_VCL is set to true if the project uses the VCL framework, and FRAMEWORK_FMX is set to true if the project uses the FireMonkey framework. The demo applications are the reference for how to set them.
The reason is that the library supports both frameworks from v3.1 onward, and the compiler has to be told which one it is compiling for. The defines are not something the library reads at runtime, they are read at compile time, which is why the setup has to happen in the project options rather than in a config file.
Then the scan manager. Create it once and use it for all frames, and the README is emphatic that it is not meant to be used by two threads at the same time. Hints are passed as a dictionary, which the scan manager frees:
var hints := TDictionary<TDecodeHintType, TObject>.Create;
hints.Add(TDecodeHintType.TRY_HARDER, nil); // slower, finds more (also rotated 1D codes)
hints.Add(TDecodeHintType.ENABLE_INVERSION, nil); // also light codes on a dark background
FScanManager := TScanManager.Create(TBarcodeFormat.Auto, hints);Three things are visible in that snippet. The hints are the standard ZXing decode hints, and both of the ones shown are switches that trade speed for coverage: TRY_HARDER is slower but finds more, including rotated 1D codes, and ENABLE_INVERSION handles light codes on a dark background. The format is TBarcodeFormat.Auto, which passes every frame to every format, and the tips section later says to use that with care.
The dictionary is freed by the scan manager, which is worth noting because it is the opposite of the usual ownership convention and a caller who frees it too would double free.
The single manager for all frames is the other design point, and it is what makes the async path cheap. A scan manager holds the decoder state, the thread pool and the format set, so reusing one across frames avoids reallocating that state sixty times a second.
The three scanning calls, and the error result that is not an error
There are three ways to scan, and they correspond to three different situations.
For a camera, scan in the background and get the result in the main thread:
FScanManager.ScanAsync(frameBitmap,
procedure(r: TReadResult)
begin
if (r <> nil) then
try
lblResult.Text := r.Text;
finally
r.Free;
end;
end,
procedure(e: Exception)
begin
lblResult.Text := e.Message;
end);Two details in that call are important in practice. The bitmap may be reused as soon as ScanAsync returns, which is what lets you hand the same frame buffer to every scan without copying, and a frame that arrives while a scan runs is skipped. The Scanning property tells you whether a scan is running, so you can decide whether to skip a frame yourself or hand it over and let it be dropped.
The result object is freed by the caller in a finally block, and the error callback receives an Exception whose message is displayed. So the async path has two failure channels: a result, and an exception.
For all barcodes in an image, with the extra information of a result:
var list := FScanManager.ScanAll(scanBitmap); // a TObjectList: frees the results
try
for var r in list do
begin
Memo1.Lines.Add(r.SymbologyIdentifier + ' ' + r.Text);
// r.Position: the 4 corners in the image, r.Orientation: the rotation in degrees
if r.IsGS1 then
Memo1.Lines.Add(r.GS1HRI); // e.g. (01)05909990329717(17)270430(10)NE76571
end;
finally
list.Free;
end;The list is a TObjectList that frees the results, and you free the list. The per-result data is where the value is for anyone doing real work: the symbology identifier tells you what you actually read, Position gives the four corners in the image, Orientation gives the rotation in degrees, and the GS1 fields handle the retail barcode case with an IsGS1 flag and the GS1HRI string that carries the application identifiers, for example (01)05909990329717(17)270430(10)NE76571.
And the third call is the one that makes this library different from a wrapper. A QR Code or Data Matrix that was found but could not be read:
FScanManager.ReturnErrors := true;
var r := FScanManager.Scan(scanBitmap);
if (r <> nil) and (r.Error <> '') then
// r.Text is empty, r.Position is where the code is: e.g. ask to hold the camera still or closer
lblStatus.Text := 'Barcode found, but not readable (' + r.Error + ')';With ReturnErrors enabled, a failed decode returns a result rather than nothing, with the text empty, the error explaining what went wrong, and the position of the code. That is a genuinely different failure mode from the usual one, and it is what lets an application ask the user to hold the camera still or move closer, instead of saying nothing happened. A code that is present, in frame, and simply not decodable is the single most common real-world scanning failure, and this API can distinguish it from a frame with no code in it at all.
The tips, and what they tell you about the performance envelope
There is a short section headed Tips and tricks, subtitled how to optimize an already fast library, and it is the most practically useful part of the README for anyone shipping this on a phone.
The first tip is to use autoformat scanning with care. With automatic on, every frame is passed to every barcode format, so if you want to scan only EAN-8 you should set the scan format for only EAN-8. That is the direct cost of the 4.0 feature set, and it is the same trade the ZXing decode hints make: the Auto format is a coverage decision, and it is paid for on every frame.
The second tip is for mobile: try not to scan every frame, skip every nth frame. The README suggests scanning four frames in a second should be good for most purposes, and that it saves CPU and battery. That number is a useful default because it tells you the decode is fast enough that you do not need every frame, and a camera pipeline that decodes at 30 frames a second is doing roughly seven times more work than it needs to.
The third tip is also for mobile: try setting your camera not to a high resolution, since 640x480 is perfect for most purposes. More resolutions means more pixels to scan means slower, and it saves CPU and battery.
Together those three tips describe the performance envelope accurately. The library is fast, but the cost is linear in pixels and in formats, and on a phone the budget that matters is battery rather than latency. Decoding a 640x480 frame four times a second is comfortable; decoding a 4K frame thirty times a second is a different proposition on the same hardware.
The demo applications in the repository back this up. There is a ZXingScanner demo built with the standard FMX components only, showing a scanner with tracked outlines and a settings screen, a vclTestApp, and a webcam demo. The settings screen in the scanner is the place where the decode switches and the format selections are exposed, with the formats split into those read by default and those only read when asked for, plus the outline color, haptic feedback and a beep.
The README also links to a real world example, ScannerMApp by Andrea Magni, with a blog post covering the move to ZXing.Delphi 4 with background decoding via ScanAsync, and the original 2017 post from before it.
Editorial conclusion
Adopt ZXing.Delphi if you are building a scanner in Delphi and want a self-contained native library rather than binding the Java or C++ originals, since the whole point is compiled-in speed with no external dependency. Do not expect it to read every code on the first try, because the error path is a first class feature and a code that is found but unreadable is a normal outcome for damaged or low-contrast labels. Before you start, set the FRAMEWORK_VCL and FRAMEWORK_FMX conditional defines on older Delphi versions, and if you are on the camera path, follow the tips on skipping frames and lowering the camera resolution, which matter more for battery life than the decoding speed does.
Frequently asked questions
What is ZXing.Delphi and what platforms does it support?
It is a native Object Pascal barcode scanning library based on the open source ZXing library, for Delphi VCL and FireMonkey, with no external libraries to link. It is compatible with Delphi 10.4 Sydney through 13.x Florence, and for XE7 through 10.3 you should use v3.13.1.
What changed in ZXing.Delphi 4.0?
The library grew from 10 symbologies to 52, adding Aztec, PDF417, Micro QR, MaxiCode, the GS1 DataBar family, DotCode, Han Xin, the stacked codes and the postal barcodes. The detectors are ports of zxing-cpp, so curved and dot-peen codes are now read, and 4.0 scans about five times faster than 3.x on a single core while trying five times as many formats.
How do I set up ZXing.Delphi in a Delphi project?
Include the source files in your project and build the ZXing.Delphi source within it. If you use a Delphi older than 11.1 you must set the FRAMEWORK_VCL or FRAMEWORK_FMX conditional define in the project options, and create one TScanManager with your decode hints and reuse it for all frames, since it is not meant to be used by two threads at once.
What does ReturnErrors do in ZXing.Delphi?
With ReturnErrors set to true, a QR Code or Data Matrix that was found but could not be read returns a result with the text empty, the error explaining the problem and the position of the code, so an application can ask the user to hold the camera still or closer instead of reporting nothing.
How do I optimise ZXing.Delphi for a mobile camera scanner?
Do not scan every frame, skip every nth frame since four frames a second should be enough, set the camera to 640x480 rather than a higher resolution, and set the scan format to the specific symbology you need instead of leaving autoformat on, which passes every frame to every format.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spelt-zxing-delphi)Was this useful?