Cross-Platform Barcode Reader with B4X
B4X is a set of rapid development tools which can be used to develop apps for all the major mobile and desktop platforms.
It has four products: B4A, B4i, B4J and B4R, which aim at Android, iOS, Java (desktop with JavaFX, server and Raspberry Pi) and Arduino development.
Apart from multi-platform support, it has the following features:
- An easy-to-use programming language.
- A full-featured dedicated IDE.
- An active and friendly community.
B4X can create native apps for Android and iOS. The B4X programming language is a modern version of Visual Basic. It will be compiled to Java for Android and Objective-C for iOS. This feature makes it easy to integrate Java/Objective-C libraries into B4X projects. The code can be shared across platforms, although we still have to write the platform-specific parts like Camera and file picker.
Dynamsoft Barcode Reader (DBR) is a barcode SDK written in C++ and provides JavaScript, Java and Objective-C packages. So it is easy to wrap it into a B4X library to develop Android, iOS and desktop barcode reader apps with B4X.
In this article, we are going to create a B4X wrapper and create demo apps for Android, iOS and desktop.
This article is Part 1 in a 3-Part Series.
Create a Test Project
Let’s first create a new B4J project to create and test the barcode library.
-
Create a new B4XPages project with B4J.
-
Open the UI designer. Add a button to pick images, a button to decode, a panel to display the image and a label to show the results.
After that, we click generate members to declare these controls and add event subs in the code.
-
Implement the image picker. Draw the image on the Panel with B4XCanvas.
Private Sub btnLoadImage_Click Dim bm As B4XBitmap Dim fc As FileChooser fc.Initialize Dim path As String=fc.ShowOpen(B4XPages.GetNativeParent(Me)) If File.Exists(path,"") Then bm=xui.LoadBitmap(path,"") cvs.ClearRect(cvs.TargetRect) drawBitmap(bm) Panel1.Tag=bm End If End Sub
Now, we have to implement the decoding part.
Wrap the Dynamsoft Barcode SDK
Let’s first write the wrapper.
Add Dependency
- Download Dynamsoft Barcode Reader’s jar file from its website.
-
Config the path of the folder of additional libraries.
- Put the jar in the additional libraries folder.
-
Reference the jar in
Main
by adding this line of code:#AdditionalJar: dynamsoft-barcodereader-8.2
How a Wrapper is Made
Basically, there are two ways to create a wrapper.
One is directly writing in Java, which is illustrated here. Creating an iOS library is a bit harder which uses Objective-C.
The other is using JavaObject to directly call Java APIs using B4X based on Java’s reflection feature. NativeObject is the equivalence of JavaObject in B4i.
It can be used with the inline java code feature. Java code can be directly included in B4X’s source code, as shown below.
#If JAVA
public String FirstMethod() {
return "Hello World!";
}
#End If
Then it can be called using JavaObject.
Dim JO as JavaObject=Me
Dim s As String = JO.RunMethod("FirstMethod", Null)
Log(s) 'will print Hello World!
Here, we choose JavaObject to create the wrapper.
Write the Wrapper
Create a class and name it DBR. It is implemented as below.
Sub Class_Globals
Private reader As JavaObject
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize
reader.InitializeNewInstance("com.dynamsoft.dbr.BarcodeReader",Null)
End Sub
public Sub initLicenseFromKey(license As String)
'request your license here: https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform
reader.RunMethod("initLicense",Array(license))
End Sub
private Sub ConvertToTextResults(results() As Object) As List
Dim list1 As List
list1.Initialize
For Each result As Object In results
Dim tr As TextResult
tr.Initialize(result) 'convert the TextResult Java object to a B4X object
list1.Add(tr)
Next
Return list1
End Sub
Sub decodeImage(bitmap As B4XBitmap) As List
Dim results() As Object
Dim SwingFXUtils As JavaObject
SwingFXUtils.InitializeStatic("javafx.embed.swing.SwingFXUtils")
Dim bufferedImage As Object=SwingFXUtils.RunMethod("fromFXImage",Array(bitmap,Null)) ' convert JavaFX image to bufferedImage
results=reader.RunMethod("decodeBufferedImage",Array(bufferedImage,""))
Return ConvertToTextResults(results)
End Sub
A TextResult class is created to represent the decoding result. Here we just parse the text and localization result.
Sub Class_Globals
Private mTextResult As JavaObject
Private mText As String
Private mResultPoints(4) As Point2D
Type Point2D(x As Int,y As Int)
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(result As Object)
mTextResult=result
Parse
End Sub
Private Sub Parse
mText=mTextResult.GetField("barcodeText")
Dim points() As Object=mTextResult.GetFieldJO("localizationResult").GetField("resultPoints")
For i=0 To 3
Dim point As JavaObject=points(i)
Dim p As Point2D
p.Initialize
p.x=point.GetField("x")
p.y=point.GetField("y")
mResultPoints(i)=p
Next
End Sub
Public Sub getObject As Object
Return mTextResult
End Sub
Public Sub getText As String
Return mText
End Sub
Public Sub getResultPoints As Point2D()
Return mResultPoints
End Sub
Use the Wrapper
Now, we can implement the decoding part with the wrapper.
-
Initialize the reader.
Sub Class_Globals Private reader As DBR End Sub Public Sub Initialize reader.Initialize reader.initLicenseFromKey("<license key>") End Sub
-
Decode the image and display the result.
Private Sub btnDecode_Click If (Panel1.Tag Is B4XBitmap)=False Then xui.MsgboxAsync("Please load an image first","") Return End If Dim bm As B4XBitmap=Panel1.Tag Dim results As List=reader.decodeImage(bm) Dim sb As StringBuilder sb.Initialize Dim color As Int=xui.Color_Red Dim stroke As Int=2 For Each result As TextResult In results sb.Append("Text: ").Append(result.Text).Append(CRLF) For i=0 To 2 Dim x1 As Int=result.ResultPoints(i).x*xPercent Dim y1 As Int=result.ResultPoints(i).y*yPercent Dim x2 As Int=result.ResultPoints(i+1).x*xPercent Dim y2 As Int=result.ResultPoints(i+1).y*yPercent cvs.DrawLine(x1,y1,x2,y2,color,stroke) Next cvs.DrawLine(result.ResultPoints(3).x*xPercent, _ result.ResultPoints(3).y*yPercent, _ result.ResultPoints(0).x*xPercent, _ result.ResultPoints(0).y*yPercent,color,stroke) Next cvs.Invalidate lblResult.Text=sb.ToString End Sub
Here is a snapshot of the completed app:
Make it Cross-Platform
We have finished the B4J application. Now, let’s modify it to make it work on Android and iOS.
Share Class Files and Layouts
Upon creation of a B4Xpage project, it will create three folders containing the platform-specific files and a Shared Files
folder for shared asset files. Shared class files are stored in the root.
Open the previous project folder. We can see a folder structure like this:
│ B4XMainPage.bas
│
├─B4A
│ │ BarcodeReader.b4a
│ │ BarcodeReader.b4a.meta
│ │ Starter.bas
│ │
│ └─Files
│ mainpage.bal
│
├─B4i
│ │ BarcodeReader.b4i
│ │ BarcodeReader.b4i.meta
│ │
│ └─Files
│ │ mainpage.bil
│ │
│ └─Special
├─B4J
│ │ BarcodeReader.b4j
│ │ BarcodeReader.b4j.meta
│ │ DBR.bas
│ │ TextResult.bas
│ │
│ └─Files
│ MainPage.bjl
│
└─Shared Files
Now we want to share the files we just created in the three platforms’ projects.
-
Move the
DBR.bas
andTextResult.bas
to the root. Open the B4A, B4J and B4i projects and re-add them as new modules using relative path. -
In B4J, open the designer. Select and copy all the controls. Open the layout files for B4i and B4A with their designer and paste.
Add Platform-Specific Code
Platform-specific code can exist in one source file using the #if statement. Let’s see it in practice.
Code for B4A
-
Use aar instead of jar.
Download the aar file from Dynamsoft, put it in the additional libraries folder and add the following line of code in Main:
#AdditionalJar: DynamsoftBarcodeReaderAndroid.aar
-
The image picker is platform-specific. We add the code in the
if b4a
statement to useContentChooser
to pick images.Private Sub btnLoadImage_Click Dim bm As B4XBitmap #if b4a Dim cc As ContentChooser cc.Initialize("CC") cc.Show("image/*", "Choose image") Wait For CC_Result (Success As Boolean, Dir As String, FileName As String) If Success Then bm=LoadBitmap(Dir,FileName) Else ToastMessageShow("No image selected", True) End If #End If #if b4j Dim fc As FileChooser fc.Initialize Dim path As String=fc.ShowOpen(B4XPages.GetNativeParent(Me)) If File.Exists(path,"") Then bm=xui.LoadBitmap(path,"") End If #End If If bm.IsInitialized And bm<>Null Then cvs.ClearRect(cvs.TargetRect) drawBitmap(bm) Panel1.Tag=bm End If End Sub
-
You can have a trial of Dynamsoft Barcode Reader for Mobile by connecting to its License Tracking Server (LTS).
Add the following inline java code to
DBR.bas
:#If b4a #if java import com.dynamsoft.dbr.BarcodeReader; import com.dynamsoft.dbr.BarcodeReaderException; import com.dynamsoft.dbr.DMLTSConnectionParameters; import com.dynamsoft.dbr.DBRLTSLicenseVerificationListener; public static void initLicenseFromLTS(BarcodeReader dbr,String organizationID){ DMLTSConnectionParameters parameters = new DMLTSConnectionParameters(); parameters.organizationID = organizationID; dbr.initLicenseFromLTS(parameters, new DBRLTSLicenseVerificationListener() { @Override public void LTSLicenseVerificationCallback(boolean isSuccess, Exception error) { if (!isSuccess) { error.printStackTrace(); } } }); } #End If #End If
Add a new sub to init license from LTS:
public Sub initLicenseFromLTS(organizationID As String) Dim JO as JavaObject=Me JO.RunMethod("initLicenseFromLTS",Array(reader,organizationID)) End Sub
In
B4XMainPage.bas
, useinitLicenseFromLTS
if the platform is mobile.Public Sub Initialize reader.Initialize #if b4j reader.initLicenseFromKey("<license key>") #else reader.initLicenseFromLTS("200001") #End If End Sub
-
In B4J, the image has to be converted while the bitmap on Android can be directly used.
Sub decodeImage(bitmap As B4XBitmap) As List Dim results() As Object #If b4j Dim SwingFXUtils As JavaObject SwingFXUtils.InitializeStatic("javafx.embed.swing.SwingFXUtils") Dim bufferedImage As Object=SwingFXUtils.RunMethod("fromFXImage",Array(bitmap,Null)) results=reader.RunMethod("decodeBufferedImage",Array(bufferedImage,"")) #else if b4a results=reader.RunMethod("decodeBufferedImage",Array(bitmap,"")) #End If Return ConvertToTextResults(results) End Sub
That’s it. We can now run the app on Android.
Code for B4i
-
Use framework instead of aar/jar.
Download the Dynamsoft Barcode Reader framework from Dynamsoft, put it in the local mac builder’s library folder and add the following line of code in Main:
#AdditionalLib: DynamsoftBarcodeReader.framework.3 #AdditionalLib: libc++.tbd
-
Use the Camera class to pick images.
#if b4i Dim cam As Camera cam.Initialize("camera",B4XPages.GetNativeParent(Me)) cam.SelectFromSavedPhotos(Sender, cam.TYPE_IMAGE) Wait For camera_Complete (Success As Boolean, Image As Bitmap, VideoPath As String) If Success Then Dim NO as NativeObject=Me bm=NO.RunMethod("normalizedImage:",Array(Image)) End If #End If
Here, the image taken in iOS has to be normalized or the image may be rotated.
The following Objective-C code is used to do this:
#if b4i #if objc //https://stackoverflow.com/questions/8915630/ios-uiimageview-how-to-handle-uiimage-image-orientation - (UIImage *)normalizedImage: (UIImage*) image { if (image.imageOrientation == UIImageOrientationUp) return image; UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale); [image drawInRect:(CGRect){0, 0, image.size}]; UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return normalizedImage; } #End If #End If
-
It is a bit different using NativeObject from JavaObject. We use inline Objective-C code to directly call DBR’s APIs and relevant B4X subs have to be modified a little.
#if b4i #If ObjC #import <DynamsoftBarcodeReader/DynamsoftBarcodeReader.h> - (DynamsoftBarcodeReader*) initializeDBR: (NSString*) license { DynamsoftBarcodeReader *dbr; dbr = [[DynamsoftBarcodeReader alloc] initWithLicense:license]; NSLog(dbr.getVersion); return dbr; } - (DynamsoftBarcodeReader*) initializeDBRFromLTS: (NSString*) organizationID { DynamsoftBarcodeReader *dbr; iDMLTSConnectionParameters* lts = [[iDMLTSConnectionParameters alloc] init]; lts.organizationID = organizationID; dbr = [[DynamsoftBarcodeReader alloc] initLicenseFromLTS:lts verificationDelegate:self]; return dbr; } - (NSArray<iTextResult*>*) decodeImage: (UIImage*) image { NSError __autoreleasing * _Nullable error; DynamsoftBarcodeReader* dbr=self->__reader.object; NSArray<iTextResult*>* result = [dbr decodeImage:image withTemplate:@"" error:&error]; NSLog(@"%lu",(unsigned long)result.count); return result; } #end if #End If
The entire decodeImage method:
Sub decodeImage(bitmap As B4XBitmap) As List #if b4i Dim results As List=asNO(Me).RunMethod("decodeImage:",Array(bitmap)) Return ConvertToTextResults2(results) #Else Dim results() As Object #If b4j Dim SwingFXUtils As JavaObject SwingFXUtils.InitializeStatic("javafx.embed.swing.SwingFXUtils") Dim bufferedImage As Object=SwingFXUtils.RunMethod("fromFXImage",Array(bitmap,Null)) results=reader.RunMethod("decodeBufferedImage",Array(bufferedImage,"")) #Else If b4a results=reader.RunMethod("decodeBufferedImage",Array(bitmap,"")) #End If Return ConvertToTextResults(results) #End if End Sub
Now, we can run the barcode reader on iOS.
Conclusion
It is easy to use B4X to create a cross-platform barcode reader. The code and UI can be shared across platforms though we still need to know a bit about native development.
Source Code
https://github.com/xulihang/BarcodeReader-B4X
The library is published on the B4X forum.
More
-
A live scanner is also implemented:
-
A web application is created using B4J+BANano:
They will be discussed in the following articles.