1) Introduction
There are lots of solutions, showing how to implement a barcode scanner into your FireMonkey app.- https://www.youtube.com/watch?time_continue=160&v=yJI2HaNMReI
- http://www.winsoft.sk/fobr.htm
- http://www.tmssoftware.com/site/blog.asp?post=280
- [...]
There are different problems with all of these solutions:
- not platform independent
- platform dependent API handling
- using external libraries
- missing code formats (f.e. no QR-Code support)
- cost a lot of money
- confusing or difficult licences
- expect another app to be installed (BarcodeScanner)
- [...]
Especially the last problem is quite uncharming, because we can't demand to install another app to use our app.
On Android the Java ZXing library is used, which is not installed by default on the customers device. So the Google Barcode Scanner app need to be installed from PlayStore.
2) Solution
A million thanks to the work of Spelt! He migrated the Java ZXing open-source API to Delphi!
And the best thing about it: It works!
The reason, why I post about external code is, because in my opinion, he didn't got enough credit for his effort.
In these times, where everywhere QR-Codes are used, a barcode scanner is fundamental!
- It's free!
- native code for Windows/Android/iOS/OSX
- Simple and fast
- Multiple barcodes: QR-Code, Code-128, Code-93, ITF
3) Integration
We want build a mini application with a single form displaying the camera, framed by a darkened area and scanning line.
The form is scanning on the fly when starting the application.
If a code was recognized, we'd like to stop scanning and showing up the content in a message dialog.
I will not explain how to build a FireMonkey form. Therefor take a look at tons of tutorials all around the internet.
[...] constructor TMyBarcodeReader.Create(); var lAppEventSvc : IFMXApplicationEventService; begin inherited; fCamera := TCameraComponent.Create(nil); fCamera.Kind := FMX.Media.TCameraKind.ckFrontCamera; fCamera.FlashMode := FMX.Media.TFlashMode.fmFlashOff; fCamera.Quality := FMX.Media.TVideoCaptureQuality.MediumQuality; fCamera.OnSampleBufferReady := doOnCameraSampleBufferReady; fFrameTake := 0; if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(lAppEventSvc)) then lAppEventSvc.SetApplicationEventHandler(AppEvent); fScanManager := TScanManager.Create(TBarcodeFormat.Auto, nil); end; procedure TMyBarcodeReader.start(); begin fResultReceived := false; fCamera.Active := false; fCamera.Kind := FMX.Media.TCameraKind.BackCamera; fCamera.Active := true; end; procedure TMyBarcodeReader.stop(); begin fCamera.Active := false; end; procedure TMyBarcodeReader.doOnCameraSampleBufferReady(Sender: TObject; const ATime: TMediaTime); begin TThread.Synchronize(TThread.CurrentThread, getImage); end; procedure TMyBarcodeReader.getImage(); var lScanBitmap : TBitmap; lReadResult : TReadResult; lImgObj : TObject; lOutBitmap : TBitmap; begin // get the TImage where we want to display the camera image if assigned(fOnDisplayImage) then fOnDisplayImage(lImgObj) else lImgObj := nil; if (not assigned(lImgObj)) or not (lImgObj is TBitmap) then exit; lOutBitmap := TBitmap(lImgObj); fCamera.SampleBufferToBitmap(lOutBitmap, true); if fScanInProgress then exit; lScanBitmap := TBitmap.Create(); lOutBitmap.Assign(lScanBitmap); TTask.Run( procedure begin try if not fResultReceived then begin fScanInProgress := true; lReadResult := fScanManager.Scan(lScanBitmap); fScanInProgress := false; end; except on E: Exception do begin fScanInProgress := false; TThread.Synchronize(nil, procedure begin // end); if assigned(lScanBitmap) then freeAndNil(lScanBitmap); exit; end; end; TThread.Synchronize(nil, procedure begin if assigned(lReadResult) then begin fResultReceived := true; if assigned(fOnResult) then fOnResult(lReadResult.Text); stop(); end; if assigned(lScanBitmap) then freeAndNil(lScanBitmap); freeAndNil(lReadResult); end); end); end; // Make sure the camera is released if you're going away function TMyBarcodeReader.appEvent(pAppEvent : TApplicationEvent; pContext : TObject) : Boolean; begin case pAppEvent of TApplicationEvent.WillBecomeInactive : fCamera.Active := false; TApplicationEvent.EnteredBackground : fCamera.Active := false; TApplicationEvent.WillTerminate : fCamera.Active := false; end; end; [...]This is just an excerpt on how to implement the API. Spelt also got a demo, where you can have a look at.
Hint:
Simply build a Form with an TImage in the back. Add 4 black rectangles with transparency as a darkened frame and a red line, symbolizing the ancient scanning line.
Then include the TMyBarcodeReader component.
Inside TMyBarcodeReader.Create() we create the logical TCameraComponent and the ScanManager. Here the TMyBarcodeReader.doOnCameraSampleBufferReady() event is applied to the TCameraComponent.
So, every time we get a new camera image, we enter this event.
constructor TMyBarcodeReader.Create(); var lAppEventSvc : IFMXApplicationEventService; begin inherited; fCamera := TCameraComponent.Create(nil); fCamera.Kind := FMX.Media.TCameraKind.ckFrontCamera; fCamera.FlashMode := FMX.Media.TFlashMode.fmFlashOff; fCamera.Quality := FMX.Media.TVideoCaptureQuality.MediumQuality; fCamera.OnSampleBufferReady := doOnCameraSampleBufferReady; fFrameTake := 0; if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(lAppEventSvc)) then lAppEventSvc.SetApplicationEventHandler(AppEvent); fScanManager := TScanManager.Create(TBarcodeFormat.Auto, nil); end;
With TMyBarcodeReader.start() we startup the scanning process, by activating the back camera.
procedure TMyBarcodeReader.start(); begin fResultReceived := false; fCamera.Active := false; fCamera.Kind := FMX.Media.TCameraKind.BackCamera; fCamera.Active := true; end;
When the camera service provides the current camera image, we automatically enter the TMyBarcodeReader.doOnCameraSampleBufferReady() event, where we synchronize our "getImage" method.
TMyBarcodeReader.getImage() is the main handling routine. At first we want to get the destination image by the "OnDisplayImage" event, to know where to display the
camera image.
// get the TImage where we want to display the camera image if assigned(fOnDisplayImage) then fOnDisplayImage(lImgObj) else lImgObj := nil; if (not assigned(lImgObj)) or not (lImgObj is TBitmap) then exit; lOutBitmap := TBitmap(lImgObj);Then we're requesting the TCameraComponent image itself directly onto our form image.
fCamera.SampleBufferToBitmap(lOutBitmap, true);In the next step we're about to run a TTask where we want to scan the latest camera buffered image.
if not fResultReceived then begin fScanInProgress := true; lReadResult := fScanManager.Scan(lScanBitmap); fScanInProgress := false; end;If we found a code, we'll stop the scanning process by deactivating camera and entering the "OnResult" event.
if assigned(lReadResult) then begin fResultReceived := true; if assigned(fOnResult) then fOnResult(lReadResult.Text); stop(); end;The last method you see, is the platform service event. When the app status changes, for example, if it's becoming inactive, we do not need to scan anymore. So we're deactivating the camera component.
function TMyBarcodeReader.appEvent(pAppEvent : TApplicationEvent; pContext : TObject) : Boolean; begin case pAppEvent of TApplicationEvent.WillBecomeInactive : fCamera.Active := false; TApplicationEvent.EnteredBackground : fCamera.Active := false; TApplicationEvent.WillTerminate : fCamera.Active := false; end; end;
4) Conclusion
As always I'd like to thank you for reading my blog. Let me know if there are some bugs, mistakes or problems with this article or component.
(*) These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval by this blog of any of the products, services or opinions of the corporation or organization or individual. This blog bears no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.