Category Archives: Technology

B4X: ABMaterial 3.75 Public/4.00 Donators released


ABMaterial Dragonfly (4.00) for B4X is available. Yes, it has a new name! What started as version 3.81 got upgraded to a new major version, especially since it uses a new cache control system.

ABMaterial has always been one of the fastest RADs from its start, now over 2 years ago.  But with 4.00, we’ll take it to a whole new level!

Mindful and I have spend 5 days (and nights) checking out how we could cache ABMaterial to the extreme without losing any of its functionality.  The results are just ridiculous…

Finish times are less than 10% of the time on the second and next loads. So on 3G, on the users next visit(s), the WebApp is almost just as fast as on high speed cable!

And this system not only works with the same page. Once one page has been loaded, all the other pages can take gain of this system. Even when the user revisits your app much later.

Furthermore there is the new debug feature to check how your apps work on different device sizes.  See it at work here:

Add this snippet to you main module, AFTER starting the server:

' in debug mode, start the browser and open the app on all devices (DOES NOT WORK IF EDGE IS YOUR DEFAULT BROWSER!)
ABM.ViewerOpenAllDevices("http://localhost:" & port & "/" & ABMShared.AppName & "/", 100)

' or open a specific device as default (DOES NOT WORK IF EDGE IS YOUR DEFAULT BROWSER!)
'ABM.ViewerOpenDevice("http://localhost:" & port & "/" & ABMShared.AppName & "/", 300, ABM.VIEWER_TABLET)

' or just open de browser, no multiple devices (should work in Edge)
'ABM.ViewerOpen("http://localhost:" & port & "/" & ABMShared.AppName & "/")
#End If

Lots of new theme properties! Check out the demo on how to use  them in the new Dragonfly theme.  As winter is coming and days are getting shorter (at least here in the northern hemisphere), I went for a ‘Night’ theme this time.

ABMaterial DragonFly is now available on 750+ locations worldwide on CDN!  If you use the CDN, you do not need to upload the css/js/fonts folders to your own server anymore!

This means only your own assets (images etc) from the www folder have to be available on your server. De demo running on for example is working like this.


ABM.ActivateUseCDN("DONATORKEY", "")

The over 20 new fixes and fulfilled wishes make ABMaterial 4.00 very stable, just like its ‘parent’ programming language: B4X.  It is a real joy knowing one can depend on a strong, bug free environment.  Erel from Anywhere Software rulez!

Happy programming!


Click here to Donation and support ABMaterial

Or make a donation with Bitcoins:

BTC: 13x7b12GonCj6iphnjLuonrwURK2jCgMdE

This is my bitcoin address as a QR code. It is possible to send bitcoins to me from mobile wallets by scanning this code.



Windows October Update ODBC export Excel problems


Windows updates, you gotta love them! This October update has a lot of developers scratching their head.

Here at OneTwo, we became first aware of the problem when some clients called our support team with the same remark: “Exporting the data to Excel does not work!”  After going through the usual procedure following the directions of Roy Trenneman from the IT crowdhave you tried turning it on and off again?“, we quickly became aware there was something more going on…

Indeed, Microsoft had rolled out a new update.  “Drop everything, we have a KB hunt on our hands!”  A time consuming thing to do, and in the meantime a lot of worried users.  Google and the MSDN site had to do overtime too.


Quite some time later, we found the little buggers. Here is the list for anyone out there banging his head aginst the wall asking himself why his code, which has been running for years, suddenly stopped working:

  • Windows 7: KB4041681
  • Windows 8.1: KB40416393
  • Windows 10: KB4040724/KB4041676

Uninstall them and reboot. Hooray, code is working again!


It looks like Windows keeps insisting to run the update.  Here is a more permanent solution.

1. Find prior version (4.0.9801.0) of msexcl40.dll

2. Place in another directory. They suggest the application directory, but since in the next step you will modify registry to point to this older version, it can probably go anywhere.

3. Update registry key HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Jet\4.0\Engines\Excel\win32 to point to the location from step 2.

You don’t even have to restart your PC.

Have a great weekend, my fellow IT minions!


Click here to Donation and support ABMaterial

Or make a donation with Bitcoins:

BTC: 13x7b12GonCj6iphnjLuonrwURK2jCgMdE

This is my bitcoin address as a QR code. It is possible to send bitcoins to me from mobile wallets by scanning this code.


B4X: An experiment for practical use of IoT on the shop floor


At OneTwo, we are always searching for ways to make things easier on the workfloor.  For nearly a decade, we introduced barcode scanners everywhere! From architects and accountants, over carpenters and contractors, to farmers and gardeners are now using our small barcode scanners to record times, jobs and used materials.

The last couple of weeks, I’ve been investigating how we could tackle another common issue, but now on the factory shop floor.  Working on a specific step in the development of a product demands the constant focus of the operator so using a barcode scanner to get the instructions is, well, just not practical if you have your hands full.

Time to get to the lab! Provided with heaps of coffee, some unhealthy snacks and an open mind we started putting some ideas together on the whiteboard.  We wanted to give as much feedback to the operator as possible using monitors,  beamer projections at the workstation and personal instructions on phones and tablets.

The key to deliver hyper-contextual content to the users is knowing the location of every chess piece in the game: the workstation, the machine that is going to be build and the operator(s).  The underlying communication technology was going to be Bluetooth Low Energy (BLE). The Raspberry Pi has it and so does almost any phone or tablet. By attaching a cheap BLE Beacon to the machine we could understand the location of all the players.

Using B4X for this project was a no-brainer, as Erels toolbox is just made to build stuff like this! One important link in the chain was missing:  the BLE reader in B4J for the Raspberry Pi.  However, writing some small scripts and calling it using jShell and a couple of timers did the job just fine.

Scripts code:

Two scripts to discover BLE devices around the Raspberry Pi.  Each one runs in its own jShell in B4J. First one scans for devices, second one reads whatever they broadcast.

# Beacon Scan by Alain Bailleul 2017
sudo timeout -s SIGINT 5s hcitool lescan

# Beacon Scan by Alain Bailleul 2017
sudo timeout -s SIGINT 5s hcidump -X > scan.txt

Also, as the Raspberry Pi has to be a BLE beacon itself (so the Android Native app can discover it), I had to write another little script:

# Beacon Scan by Alain Bailleul 2017

sudo hciconfig hci0 leadv 3
sudo hcitool -i hci0 cmd 0x08 0x0008 19 02 01 06 03 03 aa fe 11 16 aa fe 10 00 02 6f 6e 65 2d 74 77 6f 07 $1 $2 $3 00 00 00 00 00 00
sudo hciconfig

IPN=$(ip addr show eth0 | awk ‘/inet / {print $2}’ | cut -d/ -f 1)
IPW=$(ip addr show wlan0 | awk ‘/inet / {print $2}’ | cut -d/ -f 1)
MACN=$(ip link show eth0 | awk ‘/ether/ {print $2}’)
MACW=$(ip link show wlan0 | awk ‘/ether/ {print $2}’)

echo “$MACW;$IPW;$MACN;$IPN;END” > macip.txt

For anything related to presenting the instructions, we could use ABMaterial.

Alright! We’ve got everything we needed to set up a test scenario for a workstation so time to bring out the cool slide:

How it works

Although there are a lot of physical components and different OS’s in play, B4X has all the tools to make them seamlessly talk to each other using one language.  For the native Android part, I wrote my own BLE discovery library based on the altbeacons library. (Note, there is a library available in B4A to discover BLE devices).

For the video demonstration, things you have to keep in mind:


  1. OneTwo box + BLE beacon: represents a machine arriving on the workstation
  2. Raspberry Pi (under the table): represents the workstation
  3. Android Phone: represents an operator/visitor at the workstation


  1. On the monitor, for each ‘machine’ some info is presented (e.g. a serial number, to what country it has to be shipped, etc)
  2. On the phone: specific instructions for the operator(s) for this ‘machine’, or a greeting to a visitor.
  3. On the workstation, projected global instructions for the operator(s) for this ‘machine’ with a beamer.

Let’s have a look how all of this plays out:

This turned out to work all very well for a first trial! For real world usage, the project will need some more work (like improving the algorithms to discover the BLE devices, setting thresholds or calculating them without calibration, etc…).  But we are very excited with the possibilities this low-cost solution can already show in such a short time.

Next step will be setting this up on a real workstation on the shop floor in a real factory later this month.

Until next time,


Click here to Donation and get the latest ABMaterial first!



B4J:Programming LEGO Mindstorms EV3


I was wondering if it would be possible to use B4J to program the Lego Mindstorms EV3 Brick for some time. So, some weeks ago, I finally spend a couple of evenings looking into it. And yes, B4J goes Robotics! It uses a modified LeJOS Firmware.

I upgraded LeJOS to support Java 1.8, which is probably the most technical part of the project.  I cannot provide it here, as it depends on your Java version but if you follow the next steps, you can build your own.  This is done on a Windows PC:

Upgrading the LeJOS Firmware to use Java 1.8

The following procedure will install the java 8 environment compatible with leJOS 9.0 from windows 7. The only extra tool needed is 7ZIP (a free tool) which can generate tar and gz format.  In the following I use a temporary directoyr as F:\temp. Replace it with your own temp directory wherever it appears.

  1. Go to java site … 82511.html
  2. Create an account if you dont have one then Accept Licensee agreement and download ejdk-8-xxxxxxx-linux-arm-sflt-xxxxxxxx.tar.gz or the latest java8 jre for EV3.
  3. Unzip this file in your F:\temp directory. For simpler process you may rename the extracted directory  as ejdk8. Run 7zip as administrator!
  4. Go to directory ejdk8\ejdk1.8.0\bin. Save the file jrecreate.bat. Now right click on jrecreate.bat and select “modify”.  Add one first line to the bat file as SET JAVA_HOME=C:\Progra~2\Java\jdk1.8.xxxx.  this is where java8 is installed on my system. Program~2 stand for Program Files (x86) but is easier to type.
  5. Start a command window: Start->All prog->Accessory->commands.
  6. In this window type in the following commands
    cd temp
    cd ejdk8\ejdk1.8.0\bin
    jrecreate.bat –dest newjre –profile compact2 –vm clientIt should start creating the new jre files in the directory “newjre” under ejdk8\ejdk1.8.0\bin
    Wait for the final message.
    Close the command window.
  7. With file explorer go to F:\temp\ejdk8\ejdk1.8.0\bin. Copy “newjre” directory back to F:\temp
  8. Rename the “newjre” directory as something expected by LeJOS such as
  9. Right click on it. Open with 7ZIP and add to archive. Select tar format. It will create ejre-8-b132-linux-arm-sflt.tar file.
  10. Right click on tar copy then add archive with gzip format. It will create ejre-8-b132-linux-arm-sflt.tar.gz. You have created the JRE file for java8 expected by EV3.
  11. Insert your SD card and format it.
  12. Copy the ejre-8-b132-linux-arm-sflt.tar.gz under the sd card top directory.
  13. Go to C:\Program Files\leJOS EV3
  14. Now remove the SD from PV and insert the card into the EV3 brick then power it. Wait for the end of the install (about 10mn).

If anything goes wrong, format again the SD, empty the F:\temp directory and restart at step 3.  if it persist try another SD card.

Your brick is ready!

Next I wrote a B4J wrapper for the basics like motors, sensors etc. This project is just a hobby and may grow in the future. But I just wanted to share this first video (badly directed so no Oscars here, unless…)

Obviously, I wanted to test out my code, but I had to build a bot first.  Well, I was in for a surprise! It took me over 3 hours to build EV3D4 and I can tell you, I’m not a very patient person so I may have taken some shortcuts in the construction.

What happens in the video:
1. I uploaded the B4J created .jar file to the brick via ssh and I’m ready to start it.
2. After some time, it runs (on the brick shows a disclamer and waits for a key).
3. I disconnect the brick, put it on the ground and press a button on the brick.
4. I programmed the IR sensor so if it sees a wall, it turns away, else it keeps going.

Fun fact is you can use B4J-Bridge.jar on the brick!

This is handy as debugging a Brick is a b*tch. You must have a lot of patience and prepared to restart your brick A LOT. This is not a B4J limitation, just to say how unstable programming a Brick is in general.

Coding is straightforward in B4J (this is all the code I wrote, including turning if it is about to hit a wall):

Sub Process_Globals
   Private StartTimer As Timer
   Public EV3 As ABLegoEV3
   Public LeftMotor As ABLLargeRegulatedMotor
   Public RightMotor As ABLLargeRegulatedMotor
   Public Sensor As ABLIRSensor
   Public sp As ABLSensorMode

   Public control As Int = 0
   Public distance As Int = 255
End Sub

Sub AppStart (Args() As String)

   LeftMotor.Speed = 400
   RightMotor.Speed = 400
   LeftMotor.Acceleration = 800
   RightMotor.Acceleration = 800

   Log("starting sensor")
   sp = Sensor.DistanceMode 


   StartTimer.Initialize("StartTimer", 100)
   StartTimer.Enabled = True
End Sub

Sub StartTimer_Tick
   ' read the sensor
   Dim sample(sp.sampleSize) As Float
   control = Sensor.getRemoteCommand(0)
   sp.fetchSample(sample, 0)
   distance = sample(0)
   Log("Control: " & control & " Distance: " & distance) 

   If distance < 70 Then
     Log("A wall!")

     LeftMotor.rotate2(-180, True) ' start Motor.B rotating backward
     RightMotor.rotate(-180)  ' rotate C farther To make the turn
     If Bit.And(DateTime.Now, 1) <> 0 Then
         LeftMotor.rotate2(-180, True) ' start Motor.B rotating backward
         RightMotor.rotate(180)  ' rotate C farther To make the turn
         RightMotor.rotate2(-180, True) ' start Motor.B rotating backward
         LeftMotor.rotate(180) ' rotate C farther To make the turn
     End If
     Log("Let's walk")

     LeftMotor.Speed = 400
     RightMotor.Speed = 400
     LeftMotor.Backward ' my motors are installed inverted on this robot
  End If
End Sub

Sub IntroMessage()
   Dim g As ABLGraphicsLCD = EV3.GraphicsLCD
   g.drawString("Bumper Car Demo", 5, 0, 0)
   g.Font = g.Font.SmallFont
   g.drawString("Demonstration of the Behavior", 2, 20, 0)
   g.drawString("subsumption classes. Requires", 2, 30, 0)
   g.drawString("a wheeled vehicle with two", 2, 40, 0)
   g.drawString("independently controlled", 2, 50, 0)
   g.drawString("motors connected to motor", 2, 60, 0)
   g.drawString("ports B and C, and an", 2, 70, 0)
   g.drawString("infrared sensor connected", 2, 80, 0)
   g.drawString("to port 4.", 2, 90, 0)

   ' Quit GUI button:
   g.Font = g.Font.SmallFont
   Dim y_quit As Int = 100
   Dim width_quit As Int = 45
   Dim height_quit As Int = width_quit/2
   Dim arc_diam As Int = 6
   g.drawString("QUIT", 9, y_quit+7, 0)
   g.drawLine(0, y_quit,  45, y_quit) ' top line
   g.drawLine(0, y_quit,  0, y_quit+height_quit-arc_diam/2) ' left line
   g.drawLine(width_quit, y_quit,  width_quit, y_quit+height_quit/2) ' right line
   g.drawLine(0+arc_diam/2, y_quit+height_quit,  width_quit-10, y_quit+height_quit) ' bottom line
   g.drawLine(width_quit-10, y_quit+height_quit, width_quit, y_quit+height_quit/2) ' diagonal
   g.drawArc(0, y_quit+height_quit-arc_diam, arc_diam, arc_diam, 180, 90)

   ' Enter GUI button:
   g.fillRect(width_quit+10, y_quit, height_quit, height_quit)
   g.drawString2("GO", width_quit+15, y_quit+7, 0,True)

   If (EV3.Button.ESCAPE.isDown()) Then
   End If
End Sub

As I said, this is just for fun. No idea how far this will go.  You can download the source code for the B4J library from my Github.



B4J: ABTelegram bots library

Bots are the next hot thing in 2016! So in my spare-spare time I’ve been working on a wrapper for the Telegram API v2.0. It has a rather unconventional API but I’m reworking it to make it very easy to write bots in B4J.

Bots could be very useful as yet another way to ‘talk’ with IoT devices. You can control LEDs, temperatures, ask for stats etc right from within the Telegram Chat App (which runs on about every kind of platform, being the desktop, a browser, iOS, Android, etc…). Or you can, like I did to test the wrapper, write a little game:


Note: this library is in very early stages so the wrapper is not available for download yet. Full source code of the game will also be available when released.

Some sample code to show you how easy it is to write a bot in B4J (this is not the game code, just some tests):

Code to initialize the library:

Sub Process_Globals
   Dim ABT As ABTelegram
End Sub

Sub AppStart (Args() As String)
   Dim b As MyBot

End Sub

Code for the Bot:

'Class module
Sub Class_Globals
Dim ABT As ABTelegram
Public Bot As ABTLongPollingBot
Private botToken As String = &quot;207189xxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy&quot;
Private botUserName As String = &quot;zzzzzzzzzBot&quot;
'Private callbackCounter As Int
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize
Bot.Initialize(&quot;Bot&quot;, Me, botToken, botUserName)
Dim upds As List = Bot.botgetUpdates
For i = 0 To upds.Size - 1
Dim upd As ABTUpdate = upds.Get(i)
Log(&quot;Go update: &quot; &amp; upd.UpdateId)
End Sub

Sub Bot_UpdateReceived(update As ABTUpdate)
Log(&quot;Got an update&quot;)
If update.hasMessage Then
Dim msg As ABTMessage = update.GetMessage
Log(&quot;Message: &quot; &amp; msg.Text)

'        Dim userPhotos As ABTUserProfilePhotos = Bot.BotGetUserProfilePhotos(
'        Log(&quot;photos: &quot; &amp; userPhotos.TotalCount)
'        Dim Photos As List = userPhotos.Photos
'        For i = 0 To Photos.Size - 1
'            Dim photoSizes As ABTPhotoSizes = Photos.Get(i)
'            For j = 0 To photoSizes.Sizes.Size - 1
'                Dim photoSize As ABTPhotoSize = photoSizes.Sizes.Get(j)
'                Dim photoFile As ABTFile = Bot.BotGetFile(photoSize.FileId)
'                Log(photoFile.FilePath)
'                Dim job1 As HttpJob
'                job1.Initialize(&quot;Job&quot; &amp; j, Me)
'                job1.Tag = photoFile.FilePath.Replace(&quot;photo/&quot;, &quot;&quot;)
'                Dim url As String = &quot;; &amp; botToken &amp; &quot;/&quot; &amp; photoFile.FilePath
'                   job1.Download(url)
'               Next
'        Next

'        callbackCounter = callbackCounter + 1
'        Bot.BotSendMessageAsync(callbackCounter, msg.ChatId, &quot;Alweer hallo &quot; &amp; msg.Chat.FirstName)

Bot.BotSendMessage(msg.ChatId, &quot;Using an Emoji &quot; &amp; &quot;:grinning: in the message!&quot;) ' see for a list of emojis

'        Bot.BotSendPhoto(True, msg.ChatId, File.DirApp &amp; &quot;/&quot; &amp; &quot;telegram.png&quot;)

'        callbackCounter = callbackCounter + 1
'        Bot.BotSendDocumentAsync(callbackCounter, True, msg.ChatId, File.DirApp &amp; &quot;/&quot; &amp; &quot;mydocument.pdf&quot;, &quot;mydocument.pdf&quot;)

'        Bot.BotSendDocument(True, msg.ChatId, File.DirApp &amp; &quot;/&quot; &amp; &quot;mydocument.pdf&quot;, &quot;mydocument.pdf&quot;)

Bot.BotSendAudio(True, msg.ChatId, File.DirApp &amp; &quot;/&quot; &amp; &quot;myaudio.mp3&quot;)

'        Bot.BotSendVideo(True, msg.ChatId, File.DirApp &amp; &quot;/&quot; &amp; &quot;myvideo.mp4&quot;)

'        Bot.BotSendSticker(True, msg.ChatId, File.DirApp &amp; &quot;/&quot; &amp; &quot;mysticker.webp&quot;)

'        Bot.BotSendContact(msg.ChatId, &quot;+32496000000&quot;, &quot;Alain&quot;)

'        Bot.BotSendChatAction(msg.ChatId, ABT.CHATACTIONTYPE_TYPING)

'        Bot.BotSendLocation(msg.ChatId, 50.8492, 2.8779)

'        Bot.BotSendVenue(msg.ChatId, 50.8492, 2.8779, &quot;my venue&quot;, &quot;Grote markt 1&quot;)

Bot.BotSendVoice(True, msg.ChatId, File.DirApp &amp; &quot;/&quot; &amp; &quot;telegram.ogg&quot;)

'        Dim keyb As ABTReplyKeyboard
'        Dim rows As List
'        rows.Initialize
'        Dim tmpRow As ABTInlineKeyboardRow
'        tmpRow.Initialize
'        tmpRow.AddButton(&quot;test button&quot;, &quot;;, &quot;&quot;, &quot;&quot;)
'        rows.Add(tmpRow)
'        keyb.InitializeAsInlineKeyboardMarkup(rows)
'        Bot.BotEditMessageTextEx(msg.ChatId, msg.MessageId, 0, &quot;Extended test&quot;, ABT.PARSEMODE_HTML, False, keyb)

Dim rkeyb As ABTReplyKeyboard
Bot.BotSendMessageEx(msg.ChatId, &quot;some test: reply please&quot;, ABT.PARSEMODE_HTML, False, False, 0, rkeyb)

'        Dim rkeyb2 As ABTReplyKeyboard
'        Dim rows2 As List
'        rows2.Initialize
'        For i = 1 To 100
'            Dim tmpRow2 As ABTKeyboardRow
'            tmpRow2.Initialize
'            tmpRow2.AddButton(&quot;Press me&quot;)
'            rows2.Add(tmpRow2)
'        Next
'        rkeyb2.InitializeAsReplyKeyboardMarkup(rows2,False, False, False)
'        Bot.BotSendMessageEx(msg.ChatId, &quot;some test: press the button&quot;, ABT.PARSEMODE_HTML, False, False, 0, rkeyb2)

'        Dim rkeyb3 As ABTReplyKeyboard
'        Dim rows3 As List
'        rows3.Initialize
'        Dim tmpRow3 As ABTInlineKeyboardRow
'        tmpRow3.Initialize
'        tmpRow3.AddButton(&quot;Show image&quot;, &quot;&quot;, &quot;showimage&quot;, &quot;&quot;)
'        rows3.Add(tmpRow3)
'        rkeyb3.InitializeAsInlineKeyboardMarkup(rows3)
'        Bot.BotSendMessageEx(msg.ChatId, &quot;some test: press the button&quot;, ABT.PARSEMODE_HTML, False, False, 0, rkeyb3)
End If
If update.hasCallbackQuery Then
Dim cquery As ABTCallbackQuery = update.GetCallbackQuery
Log(&quot;CallbackQuery: &quot; &amp;
Select Case cquery.Data
Case &quot;showimage&quot;
Dim msg As ABTMessage = cquery.Message

Bot.BotSendPhotoEx(True, msg.ChatId , File.DirApp &amp; &quot;/&quot; &amp; &quot;myphoto.jpg&quot;, &quot;A new photo of darth vader!&quot;,False, 0, Null )
Bot.BotSendLocation(msg.ChatId, 50.8492, 2.8779)
Bot.BotEditMessageText(msg.ChatId, msg.MessageId, &quot;&quot;, &quot;Shown&quot;)
End Select
End If
If update.hasInlineQuery Then
Dim ciquery As ABTInlineQuery = update.GetInlineQuery
Log(&quot;InlineQuery: &quot; &amp; ciquery.Query)
If ciquery.Query = &quot;loc&quot; Then
Dim lstResults As List
Dim myanswer As ABTInlineQueryResult
myanswer.InitializeAsLocation(&quot;1&quot;, 50.8492, 2.8779, &quot;Your location&quot;)
Bot.BotAnswerInlineQuery(ciquery.Id, lstResults)
End If
End If
If update.hasChosenInlineResult Then
Dim ccresult As ABTChosenInlineResult = update.GetChosenInlineResult
Log(&quot;ChosenInlineResult: &quot; &amp; ccresult.Query)
End If
End Sub

Sub Bot_AsyncSendReceived(methodType As String, callbackId As String, success As Boolean, asyncObject As ABTObject)
Log(&quot;Async method: &quot; &amp; callbackId &amp; &quot; &quot; &amp; methodType &amp; &quot; &quot; &amp; success)
If success And asyncObject &amp;lt;&amp;gt; Null Then
If asyncObject.objectType=&quot;ABTMessage&quot; Then
Dim message As ABTMessage = asyncObject
Log(&quot;Async callback: &quot; &amp; message.MessageId)
End If
End If
End Sub

Sub JobDone (Job As HttpJob)
Log(&quot;JobName = &quot; &amp; Job.JobName &amp; &quot;, Success = &quot; &amp; Job.Success)
If Job.Success = True Then
Dim fSave As OutputStream = File.OpenOutput(File.DirApp, Job.tag, False)
File.Copy2(Job.GetInputStream, fSave)
Log(&quot;Error: &quot; &amp; Job.ErrorMessage)
End If
End Sub


Click here to Donation if you like my work


B4R (Basic4Arduino) is coming!

Excellent news from Erel (CEO of Anywhere Software).  The excellent B4X suite will support yet another platform: Arduino!  Next to support for all desktop platforms, webapps, Raspberry Pi, Android and iOS this new addition is another great step from Anywhere Software towards the most simple framework to make IoT applications.

Unlike the other platforms which generate Java, B4R will generate native C.  Users will be able to write their own libraries or use inline C for specific functionalities.  More on this later, but make sure you check out the video!



Xojo: Sketching with Real-time Contrast Preserving Decolorization and Potrace


Always looking for some ways to learn the computer to ‘see’, I came accross two very interesting algorithms: ‘Real-time Contrast Preserving Decolorization’ by Cewu Lu, Li Xu and Jiaya Jia. And Potrace by Peter Selinger

Decolorization – the process to transform a color image to a grayscale one – is a basic tool in digital printing, stylized black-and-white photography, and in many single channel image processing applications. RTCPD is aiming at maximally preserving the original color contrast alleviate a strict order constraint for color mapping based on human vision system.

An example can be seen here:

The second algorithm Potrace(TM) is a tool for tracing a bitmap, which means, transforming a bitmap into a smooth, scalable image.


Playing around with both algorithms, I came up with the idea to use them for a project I had in mind for some time: Sketching pictures on my wall using a Pico projector.

First I had to implement both algorithms in Xojo. The result of RTCPR can be found in the attached source code in the RTCPRGB2Gray() method:

Private Sub RTCPRGB2Gray(pic as Picture)
  dim imcols as Integer = pic.Width
  dim imrows as Integer = pic.Height
  dim sRGB as RGBSurface = pic.RGBSurface

  dim Mat(-1,-1) as ABDoubleVector
  redim Mat(imrows-1,imcols-1)
  dim c as Color
  for y as Integer = 0 to  imrows - 1
    for x as Integer = 0 to imcols - 1
      c = sRGB.Pixel(x,y)
      mat(y,x) = new ABDoubleVector(c.Blue, c.Green, c.Red) ' switch to BRG

  dim s as Double = 64.0 / sqrt(imcols*imrows)
  dim cols as Integer = (s * imcols) + 0.5
  dim rows as Integer = (s * imrows) + 0.5

  dim sigma as Double = 0.05
  dim W() as ABDoubleVector = wei()

  dim P() as ABDoubleVector
  dim det() as Double

  dim pos0() as ABDoublePoint
  dim pos1() as ABDoublePoint
  for i as Integer = 0 to cols - 1
    for j as Integer = 0 to rows - 1
      dim x as Integer = (i+0.5) * imcols/cols
      dim y as Integer = (j+0.5) * imrows/rows
      pos0.Append new ABDoublePoint(x,y)
      pos1.Append new ABDoublePoint(x,y)


  for i as Integer = 0 to pos0.Ubound
    dim c0 as ABDoubleVector = mat(pos0(i).y, pos0(i).x)
    dim c1 as ABDoubleVector = mat(pos1(i).y, pos1(i).x)

    add c0,c1, P, det

  cols = cols / 2
  rows = rows / 2

  for i as Integer = 0 to cols - 2
    for j as integer = 0 to rows - 1
      dim x0 as integer = (i + 0.5) * imcols / cols
      dim x1 as integer = (i + 1.5) * imcols / cols
      dim y as integer = (j + 0.5) * imrows / rows

      dim c0 as ABDoubleVector = mat(y, x0)
      dim c1 as ABDoubleVector = mat(y, x1)

      add(c0, c1, P, det)

  for i as integer = 0 to cols - 1
    for j as Integer = 0 to rows - 2
      dim x as integer = (i + 0.5) * imcols / cols
      dim y0 as integer = (j + 0.5) * imrows / rows
      dim y1 as integer = (j + 1.5) * imrows / rows

      dim c0 as ABDoubleVector = mat(y0, x)
      dim c1 as ABDoubleVector = mat(y1, x)

      add(c0, c1, P, det)

  dim maxES as Double = -99999999999
  dim bw as Integer
  for i as integer = 0 to w.Ubound
    dim es as Double
    for j as integer = 0 to p.Ubound
      dim l as Double = P(j).x * W(i).x + P(j).y * W(i).y + P(j).z * W(i).z
      dim detM as Double = det(j)

      dim a as double = (L + detM) / sigma
      dim b as double = (L - detM) / sigma

      es = es + log(exp(-a * a) + exp(-b * b))
    es = es / (p.Ubound+1)
    if eS &gt; maxEs then
      maxEs = Es
      bw = i
    end if

  dim result(-1,-1) as Double
  redim result(imrows-1,imcols-1)

  for y as Integer = 0 to  imrows - 1
    for x as Integer = 0 to imcols - 1
      result(y,x) = mat(y,x).z*W(bw).x + mat(y,x).y*w(bw).y+0
      result(y,x) = mat(y,x).x*W(bw).z + result(y,x)*1+0
      sRGB.Pixel(x,y) = rgb(result(y,x),result(y,x),result(y,x))

  bw = 0
End Sub

Potrace is a complete framework to trace an image and converting it to Xojo did take some time. While writing it, I also made some changes to it to optimize the result. One of it was doing my own thresholding using the surrounding pixels to smoothen the edge detection.


  dim v, v1, v2, v3, v4, v5, v6, v7, v8 as Integer
  dim ex,ey, weight, weighttotal, total as Double

  'SISThreshold2 alain
  for y = 1 to H - 2
    for x = 1 to W - 2
      v = sDebugPicRGB.Pixel(x, y).red
      v1 = sDebugPicRGB.Pixel(x+1, y).red
      v2 = sDebugPicRGB.Pixel(x-1, y).red
      v3 = sDebugPicRGB.Pixel(x, y+1).red
      v4 = sDebugPicRGB.Pixel(x, y-1).red
      v5 = sDebugPicRGB.Pixel(x+1, y-1).red
      v6 = sDebugPicRGB.Pixel(x-1, y-1).red
      v7 = sDebugPicRGB.Pixel(x+1, y+1).red
      v8 = sDebugPicRGB.Pixel(x-1, y+1).red
      ex = (v1+v2+v5+v6)/4
      ey = (v3+v4+v7+v8)/4
      if ex &gt; ey then
        weight = ex
        weight = ey
      end if
      weighttotal = weighttotal + weight
      total = total + weight * (v)

  dim  Threshold as Integer = 128

  if weighttotal &lt;&gt; 0 then
    Threshold = total/weighttotal
  end if

  dim ret as Boolean = (sDebugPicRGB.Pixel(0, 0).Red &gt;= Threshold)

  for y = 0 to H
    for x = 0 to W
      c = sDebugPicRGB.Pixel(x, y)
      if &gt;= Threshold then
        Matrix(x+1, y + 1) =true
        Matrix(x+1, y + 1) = false
      end if

  for x = 0 to MatrixW
    Matrix(x,0) = true
    Matrix(x, MatrixH) = true
  for y = 0 to MatrixH
    Matrix(0,y) = true
    Matrix(MatrixW, y) = true

This gave me all the tools to start tracing an image. But for my project I wanted to have a sort of ‘sketchy’ feeling. For this I needed a couple of things because I wanted the drawing and painting to be done gradually. I had to build it with delays, repetition, randomization and color fading. I wanted to start from a picture and create something like this:

DrawImage() does all that. It first draws the beziers with some little random mistakes in grey. A gradual painting is then used to fill in the polygons. Finaly it repeats the process with the correct lines in white.

You may notice that a lot of code is redundant, but this is intended because I needed to have enough delays to mimic a more natural feeling.

Here is a demo video showing the result of this project on my wall. It takes about ten seconds before it starts drawing as I had to grab my camera :-). Five different images are drawn.

The project with full source code can be downloaded here



Click here to Donation if you like my work

%d bloggers like this: