Tag Archives: Tutorial

B4J: Creating a Star Rating component in ABMaterial

StarRating

MichaΕ‚ asked me in the feedback app to implement some kind of Star Rating component. As this is not so commonly needed, I thought this could be another good example of an ABMCustomComponent. One can ‘hover’ over the stars to set its value or it can be set and retrieved by code.

I show you how to do it in ABMaterial 3.75 first with the new features (released soon), and will then explain what needs to be changed for previous versions.

So let’s get started!

1. We create a new standard class CompStarRating:

'Class module
Sub Class_Globals
Public ABMComp As ABMCustomComponent
Dim myInternalName As String
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String, radioName As String)
myInternalName = radioName
Dim CSS As String = $"
#half-stars-example .rating-group {
display: inline-flex;
}
#half-stars-example .rating__icon {
pointer-events: none;
}
/* This is an important part: we MUST override the Materialize CSS implementation of the the radio button! */
#half-stars-example .rating__input + label:before,
#half-stars-example .rating__input + label:after {
position: absolute !important;
left: -9999px !important;
}
#half-stars-example .rating__label {
cursor: pointer;
padding: 0 0.1em;
font-size: 2rem;
margin-top: 1rem;
}
#half-stars-example .rating__label--half {
padding-right: 0;
margin-right: -0.6em;
z-index: 2;
}
#half-stars-example .rating__icon--star {
color: orange;
}
#half-stars-example .rating__icon--none {
color: #eee;
}
#half-stars-example .rating__input--none:checked + .rating__label .rating__icon--none {
color: red;
}
#half-stars-example .rating__input:checked ~ .rating__label .rating__icon--star {
color: #ddd;
}
#half-stars-example .rating-group:hover .rating__label .rating__icon--star,
#half-stars-example .rating-group:hover .rating__label--half .rating__icon--star {
color: orange;
}
#half-stars-example .rating__input:hover ~ .rating__label .rating__icon--star,
#half-stars-example .rating__input:hover ~ .rating__label--half .rating__icon--star {
color: #ddd;
}
#half-stars-example .rating-group:hover .rating__input--none:not(:hover) + .rating__label .rating__icon--none {
color: #eee;
}
#half-stars-example .rating__input--none:hover + .rating__label .rating__icon--none {
color: red;
}"$
ABMComp.Initialize("ABMComp", Me, InternalPage, ID, CSS)
End Sub

Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String
Return $"
<div id="half-stars-example">
<div id="${internalID}" class="rating-group"><input id="rating-0" class="rating__input rating__input--none" checked="checked" name="${myInternalName}" type="radio" value="0" />
<label class="rating__label" title="0 stars" for="rating-0">Β </label>
<input id="rating-00" class="rating__input" type="radio" value="-1" />
<label class="rating__label rating__label--half" title="0.5 stars" for="rating-05"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-05" class="rating__input" name="${myInternalName}" type="radio" value="0.5" />
<label class="rating__label" title="1 star" for="rating-10"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-10" class="rating__input" name="${myInternalName}" type="radio" value="1" />
<label class="rating__label rating__label--half" title="1.5 stars" for="rating-15"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-15" class="rating__input" name="${myInternalName}" type="radio" value="1.5" />
<label class="rating__label" title="2 stars" for="rating-20"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-20" class="rating__input" name="${myInternalName}" type="radio" value="2" />
<label class="rating__label rating__label--half" title="2.5 stars" for="rating-25"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-25" class="rating__input" name="${myInternalName}" type="radio" value="2.5" />
<label class="rating__label" title="3 stars" for="rating-30"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-30" class="rating__input" name="${myInternalName}" type="radio" value="3" />
<label class="rating__label rating__label--half" title="3.5 stars" for="rating-35"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-35" class="rating__input" name="${myInternalName}" type="radio" value="3.5" />
<label class="rating__label" title="4 stars" for="rating-40"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-40" class="rating__input" name="${myInternalName}" type="radio" value="4" />
<label class="rating__label rating__label--half" title="4.5 stars" for="rating-45"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
<input id="rating-45" class="rating__input" name="${myInternalName}" type="radio" value="4.5" />
<label class="rating__label" title="5 stars" for="rating-50"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
<input id="rating-50" class="rating__input" name="${myInternalName}" type="radio" value="5" /></div>
</div>
"$
End Sub

public Sub GetCurrentRating(InternalPage As ABMPage) As Double
Dim script As String = $"return $('input[name=${myInternalName}]:checked').val();"$
Dim ret As Future = InternalPage.ws.EvalWithResult(script, Null)
InternalPage.ws.Flush

Return ret.Value
End Sub

public Sub SetCurrentRating(InternalPage As ABMPage, value As Double) {
Dim script As String = $" $('input[name=${myInternalName}][value="${value}"]').prop('checked', 'checked');"$
InternalPage.ws.Eval(script, Null)
InternalPage.ws.Flush
End Sub

' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
Dim script As String = $""$
InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
' flush not needed, it's done in the refresh method in the lib
End Sub
' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
Dim script As String = $""$
InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
End Sub
' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
End Sub

ABMaterial users will notice I have added the CSS directly into the component. By doing so, we do not need to use the page.AddExtraCSSFile method in the Page_Build() method. (you can still do it this way, and in some cases it may be better as this part can be big. And in the final version, compress it!). In ABMaterial before version 3.75, using AddExtraCSSFile is the only way possible.

The only difficult part in the CSS was I had to override Materialize CSS’s implementation of a radio button (the ‘circle’). So we had to get rid of it.

2. Now we can make a variable for our StarComponent in the page Class_Globals, as we want to be able to Set and Get the value:

Dim myRating As CompStarRating

3. In Page_Connect() we add our component and a couple of buttons:

...
myRating.Initialize(page, "myRating", "rating")
page.Cell(1,1).AddComponent(myRating.ABMComp)

Dim btn As ABMButton
btn.InitializeFlat(page, "btn", "", "", "Get value", "")
page.Cell(2,1).AddComponent(btn)

Dim btn2 As ABMButton
btn2.InitializeFlat(page, "btn2", "", "", "Set 2.5 stars value", "")
page.Cell(3,1).AddComponent(btn2)
...
page.Refresh
...

4. And our Get and Set code in the buttons:

Sub btn_Clicked(Target As String)
Dim value As Double = myRating.GetCurrentRating(page)
Log(value)
End Sub

Sub btn2_Clicked(Target As String)
myRating.SetCurrentRating(page, 2.5)
End Sub

That is all! :)

Changes for versions of ABMaterial before 3.75

1. Save the CSS string into a text file (e.g. star.rating.css) and copy it to the /css/custom folder.
2. Remove the CSS param from ABMComp.Initialize(“ABMComp”, Me, InternalPage, ID, CSS)

ABMComp.Initialize("ABMComp", Me, InternalPage, ID) '<-- CSS param deleted

3. In Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String remove the InteranlPage param:

Sub ABMComp_Build(internalID As String) As String

4. In Page_Build(), load the css file you created:

page.AddExtraCSSFile("custom/star.rating.css")

Alwaysbusy

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.

13x7b12GonCj6iphnjLuonrwURK2jCgMdE

Advertisements

B4i: 1 … 2 … 3 … Ready … Go!

B4i2
This weekend I’ve found some time to setup my testing environment for B4i, the latest brainchild of Anywhere Software. I must say, being a novice Mac user, setting everything up went very smooth.

The only parts I got into some trouble was my own fault, as I went though the setup tutorials, videos and Beginners Guide just a little bit to fast. Eager to get started, I skimmed through them and forgot some important steps.

First I paid my obligatory taxes to Apple: $99/year. (Programming for Android only sets you back with a one time fee of $25). Only a couple of minutes later, my account was activated.

One part that may have gone a bit to fast for a first time Apple developer was the creation of an App ID. As Erel had his system already setup when he made the video and could just pick an App ID, he did not show us how to create it in the first place. It is mentioned further in the post you can create a single wildcard App ID if you put a .* at the end, but this was not very clear from the tutorial. So, this is how I did it:

Click to enlarge

Click to enlarge

Another RTFM moment was when I wanted to install the B4i-Bridge app on the device. I started watching the video and forgot to read the bold sentence above it:

Before you install B4i-Bridge you must install the B4I certificate. This step is not shown in the video. Open Safari (device browser) and navigate to: www.b4x.com/ca.pem

Clearly stated, but hey, I was in a hurry…

I first tried the Hosted Builder option to compile the app. Very smooth and a excellent alternative for Windows developers who do not own a Mac. And for $26 a year, a bargain.

But, as I want to experiment with creating libraries myself in Objective-C, I wanted to install the local MacBuildServer. Again, following the tutorial, everything went very well. Downloading XCode took most of the time.

One note: Make sure your Mac is in the same IP range as the rest of your development environment. At first, the Mac had IP 192.168.40.116 while the rest was in the 192.168.1.x range. So it didn’t work.

The rest was pure cosmetic. I added an shortcut on the Mac to start the MacBuildServer, and one on the PC side to shut it down.

Creating the shortcut on the Mac side went like this:

  • Open up a terminal
  • go to the folder where you unzipped the macserver-aa (in my case, it’s on the desktop, so it looked like this:

    $ cd desktop
    $ cd macbuilder-aa

  • create a text file

    $ shout start.command

  • add the following lines (adjust the cd to the path where your MacBuildServer is)

    #!/bin/sh
    cd /Users/Alwaysbusy/Desktop/macserver-aa
    java -jar B4iBuildServer.jar

  • save and in the terminal type:

    $ chmod -x start.command

  • Right click on start.command, pic ‘Get Info’ in the menu and rename it to something like ‘B4i Build Server Start.command’.
    Click ‘Hide extension’
  • And change the icon to a nice B4i one. I’ve ripped the B4i icon from the exe (sorry Erel) and saved it as a .png. In case you need it, here it is:
    B4i
    Open the png on the Mac in preview and copy it (Edit – Copy). In the ‘Info Panel’ of the command file, click on the icon until it gets a blue rectangle. Then you can do ‘Edit – Past’.
    In my case, it looked like this:

B4i3

Coming from Windows and being used to creating .bat files, this is all rather complicated on a Mac I must say.

On the PC side I created also the icon to shut the MacBuildServer down. Enter http://:51041/kill in your favorite browser. Create a bookmark and drag it to your desktop. Rename it to something like ‘B4i Build Server Kill’. You can also change the icon:

  • Right click on the shortcut and pick ‘Properties’
  • Press ‘change icon’
  • Browse to where you have installed B4i
  • Pick B4i.exe and select the icon

So (besides my shortcut creation problems on a Mac), setting up B4i is a breeze! I’m ready to add some serious iOS programming experience to my portfolio.

Get B4i now for only $59 from the Anywhere Software Store!

Here are some quick links to the tutorial parts I used:
Creating a certificate and provisioning profile
Installing B4i-Bridge and debugging first app
Local Mac Builder Installation

See ya!

Alwaysbusy


Xojo: Comanche 3D in about 100 lines UPDATE

Tomas made HUD and Maps active!

Tomas made HUD and Maps active!

A quick update with a much smoother and faster version of the ABComanche article. This one is using some tricks out of the ABXVision framework using Memoryblocks and pointers. (Don’t worry, sound worse than it is).

You can download this new version here

I’ve been amazed how many people enjoyed playing with this! Some of you made great additions like the HUD display, camera views and added sound. Thanks Tomas Jakobs for this! Will Shank even made a OpenGL viewer for the maps. It’s not using the voxel engine discribed in the previous article but it looks great!

Will Shanks OpenGL version!

Will Shanks OpenGL version!

Check out the Xojo forum post for their creations!

Cheers!

Alwaysbusy

Click here to Donation if you like my work


Xojo: Building Comanche 3D in 100 lines

Comanche: Maximum Overkill

Comanche: Maximum Overkill

If you are a child of the 90s, you certainly will remember Comanche: Maximum Overkill. The graphics were awesome and way ahead of its time. Remember, we did’t have fancy 3D accelerated graph cards. They used a technique called voxel space. Using only a picture containing the colors and another one with the depths they displayed wonderful landscapes on your screen. With your Comanche chopper you could fly over this landscape, diving towards the enemy. It was fun! I’ve must have spend hundreds of hours playing with this.

It is surprising how little code is needed to reproduce this experience. In this article, we’ll build the 3D world and will be able to fly, dive etc over the landscape with Xojo in less than 100 lines of code! The code is based on the work of Sebastian Macke.

First some background on the algorithm. Voxel Space is very similar to raycasting (the technique used in games like *Wolfenstein 3D, the old one). The algorithm draws simply perspectively correctly vertical lines by shooting a ray from the camera into the map. This image illustrates this:

Voxel Space Raytracing

Voxel Space Raytracing

*Note: I first had Doom also listed but thedeemon at Reddit pointed out Doom uses another algorithm, BSP trees (binary space partition).

The algorithm goes like this:
1. For every column of the screen define a light ray which starts at the camera and ends at the horizon somewhere far away.
2. Divide the ray into very small segments so that every pixel lying on the map is considered.
3. Load the height and color from the 2D maps corresponding to the position on the ray.
4. Do some clever perspective corrections for the height coordinate. This is the hard part of the algorithm but it uses some easy math.
5. Draw a vertical line with the corresponding color with the height retrieved from the perspective correction.

So we need two pictures to do this. One containing the colors and one with the depth levels (0 – 255). The original Comanche game used 1024×1024 pictures, so that’s what we are going to do. Here is an example of both maps:

Example color and depth maps

Example color and depth maps

Ok, let’s do some code! This is what we’re going to build:

Example screen of what we will build

Example screen of what we will build

In the Open event we initialize some pictures

Sub Open()
  ' initialize the buffers
  mBuffer = new Picture(1024,768,32)
  mBuildBuffer = new Picture(VoxWidth, VoxHeight, 32)
  mBuildBufferRGB = mBuildBuffer.RGBSurface

  ' create the chopper picture with the transparent areas
  Chopper = new Picture(1024,768,32)
  Chopper.Graphics.DrawPicture Comanche,0,0
  Chopper.Mask.Graphics.DrawPicture ComancheMask,0,0

  ' load a map
  CurrentMap = 1
  LoadMap(CurrentMap)
End Sub

LoadMap is just a simple method to load the maps and reinitialize our start position:

Sub LoadMap(MapID as integer)
  dim x,y as Integer
  'Initialize the position and the lookup/lookdown vars
  xp = 512
  yp = 800
  hp = -50
  vp = -100

  ' prebuild the tables holding the pixel data of the map and depthmap to speed things up
  select case MapID
  case 1
    VoxelMapRGB = C1W.RGBSurface
    for y = 0 to 1023
      for x = 0 to 1023
        VoxelHeight(y,x) = C1D.RGBSurface.Pixel(y,x).Red
      next
    next
  case 2
    VoxelMapRGB = C2W.RGBSurface
    for y = 0 to 1023
      for x = 0 to 1023
        VoxelHeight(y,x) = C2D.RGBSurface.Pixel(y,x).Red
      next
    next
  case 3
    VoxelMapRGB = C3W.RGBSurface
    for y = 0 to 1023
      for x = 0 to 1023
        VoxelHeight(y,x) = C3D.RGBSurface.Pixel(y,x).Red
      next
    next
  case 4
    VoxelMapRGB = C4W.RGBSurface
    for y = 0 to 1023
      for x = 0 to 1023
        VoxelHeight(y,x) = C4D.RGBSurface.Pixel(y,x).Red
      next
    next
  end select

  ' Draw!
  Update
End Sub

Now, we just have to make our algorithm. The Update function will go over every vertical line and calculate what needs to be drawn to mimic the 3D view.

Sub Update()
  dim i as Integer
  dim x3d, y3d, rotx, roty as Double

  ' draw the sky first on the end picture
  mBuildBuffer.Graphics.DrawPicture Sky,0,0

  y3d = -VoxDepth

  ' update the view. Calculate the rotation and raycast every line
  while i &lt; VoxWidth
    x3d = (i - VoxWidth / 2) * 1.5

    rotx =cos(angle) * x3d + sin(angle) * y3d
    roty = -sin(angle) * x3d + cos(angle) * y3d

    raycast(i, xp, yp, rotx + xp, roty + yp, y3d / sqrt(x3d * x3d + y3d * y3d))
    i = i + 1
  wend

  ' draw the result on the end picture
  mBuffer.Graphics.DrawPicture mBuildBuffer, 0,0, 1024, 512, 0,0,512,256
  ' draw the chopper overlay
  mBuffer.Graphics.DrawPicture Chopper,0,0

  ' refresh the canvas
  Canvas1.Refresh(False)
End Sub

And our Raycast method that will build the result:

Sub Raycast(line as integer, x1 as double, y1 as double, x2 as double, y2 as double, d as double)
  ' The magic! Raycast on the current line. 

  ' line: vertical line to compute
  ' x1, y1: initial points on map for ray
  ' x2, y2: final points on map for ray
  ' d: correction parameter for the perspective

  dim dx,dy as Double
  dx = x2 - x1
  dy = y2 - y1

  dim r as Double = sqrt(dx * dx + dy * dy)
  dx = dx / r
  dy = dy / r

  dim yMin as Integer = 256
  dim i,k as Integer
  dim h as Integer
  dim ci as Color
  dim y3 as Double
  dim z3 as Integer

  while i &lt; r-20
    i = i + 1
    x1 = x1 + dx
    y1 = y1 + dy
    if x1 &gt; 0 and y1 &gt; 0 and x1 &lt; 1024 and y1 &lt; 1024 then
      ' get the height
      h = VoxelHeight(x1, y1)
      ' get the color
      ci = VoxelMapRGB.Pixel(x1, y1)

      ' calculate the ray depth. Do some perspective correction for the height.
      h = 256 - h
      h = h - 128 + hp
      y3 = abs(d) * i
      z3 = h / y3 * 100 - vp

      if (z3 &lt; 0) then z3 = 0
      if (z3 &lt; voxHeight) then
        k = z3
        While k &lt; ymin
          ' draw a vertical line with the corresponding color with the height retrieved from the perspective correction
          mBuildBufferRGB.Pixel(line, k) = ci
          k = k + 1
        wend
      end if

      if (ymin &gt; z3) then
         ymin = z3
      end if
    end if
  wend
End Sub

Finally the key handeling on the Canvas:

Sub KeyDown(Key as String) as Boolean
    elect case key
  case &quot;4&quot; ' left
    angle = angle + 0.1
  case &quot;6&quot; ' right
    angle = angle - 0.1
  case &quot;8&quot; ' forward
    xp = xp - 2.0 * sin(angle)
    yp = yp - 2.0 * cos(angle)
  case &quot;2&quot; ' backward
    xp = xp + 2.0 * sin(angle)
    yp = yp + 2.0 * cos(angle)
  case &quot;7&quot; ' up
    hp = hp + 2
  case &quot;1&quot; ' down
    hp = hp - 2
  case &quot;9&quot; ' look up
    vp = vp + 2
  case &quot;3&quot; ' look down
    vp = vp - 2
  case &quot;m&quot;, &quot;M&quot; ' cycle through the maps
    CurrentMap = CurrentMap + 1
    if CurrentMap = 5 then CurrentMap = 1
    LoadMap(CurrentMap)
    Return true
  end select

  ' update with the new params
  Update

  Return true
End Sub

Note: Alwyn Bester noticed some problems with the key navigation and did this to fix it:

…Just a quick one, not sure if it’s a focus thing on Windows, but I had to move the KeyDown code from the Canvas event to the keydown event of the Form, for the navigation to work…

Done! Here is a video demonstrating what we just build with 100 lines of Xojo code:

Are Voxel engines a thing of the past? I don’t think so. As a matter of fact they are very hot nowadays. Being able to use 3D acceleration is producing some interesting engines like VoxelFarm and Atomontage. Check this last one out if you are interested in such engines.

Note: This is the original version of this article. There is a newer faster version in this article UPDATE

You can download the source code here.
Or the binaries here if you do not have Xojo.

The keys to play with it are:

Use the KeyPad!

Use the KeyPad!

Note: “M” to cycle through the maps!

See ya!

Alwaysbusy

Click here to Donation if you like my work


Xojo ABXVision – 2D Augmented Reality example

DISCONTINUED: Interested parties in purchasing the source code can contact me via email.

2D Augmented Reality Example

2D Augmented Reality Example

Ready for some fun stuff? Let’s play with ABXVision and create the 2D Augmented Reality program shown in the teaser video.

A quick note first.
I did not find a suitable free Webcam framework for OSX. I talked with Christian from MonkeyBread about creating a cheap spinoff of his excellent plugin suite that just contains the Webcam functionality, but it appears it’s not that easy to make. (I’m saving up myself to buy them also, so some donations would be welcome) If someone knows an alternative (free or not) that works well with ABXVision, or finds it a challenge to write one, mail me. I’m sure a lot of OSX peeps would love this.

If no webcam is found this demo will use an included picture to demonstrate the workings of ABXVision. If you already have the MBS plugins, you can make the changes in the code. I’ll point out where it is needed.

Enough about that, Alain. We want some code!

And right you are! Here we go.

We’ll need a couple of steps to go from our grabbed webcam picture to the augmented one.

Here are the steps we’re going to do. You’ll notice there are quite a few and we want to do this real-time. Thanks to our ABXVImage from the previous article, we’ll be able to do so. (Click to enlarge)

The flow

The flow

First we need to do some initialization in the open event of the form. I’ve documented the code.

   ' pauze the loop
  Timer1.Enabled = false

  ' set DEBUG on to show extra info (like corners)
  Debug = true

  ' camera initialization
  CameraWidth = 320
  CameraHeight = 240

  ' Windows ONLY! for OSX, this should be from the plugin you are using!
  #if TargetWin32
    MyCam = new ABXVWebCamWindows
    dim Ret as boolean
    Ret = MyCam.Start(canCam, cameraWidth, cameraHeight)
    if ret = false then
      MsgBox &amp;amp;quot;Unable to initialize the webcam! The demo program will continue using the demo picture.&amp;amp;quot;
      canCam.Backdrop = DemoPicture
    end if
  #else
    ' write here the code to initialize your webcam plugin
    ' for this demo, I'll use a static Picture
    canCam.Backdrop = DemoPicture
  #endif

  CameraFocalLength = CameraWidth ' the width of the picture is a good autoFocusLength
  'Glyp initialization
  GlyphSizeMM= 113 ' in milimetre, the real length
  GlyphSizeMMHalf = glyphSizeMM / 2

  ' a list of all the sizes of Glyphs we are going to have in our database
  GlyphSizes = Array(5,8)
  ' add some glyph templates to our database
  AddGlyphTemplate(cornerTemplate, &amp;amp;quot;Alwaysbusy's Corner&amp;amp;quot;, &amp;amp;quot;&amp;amp;quot; + _
  &amp;amp;quot;00000&amp;amp;quot; + _
  &amp;amp;quot;01100&amp;amp;quot; + _
  &amp;amp;quot;00100&amp;amp;quot; +_
  &amp;amp;quot;01010&amp;amp;quot; + _
  &amp;amp;quot;00000&amp;amp;quot;, 5)
  AddGlyphTemplate(xojoTemplate, &amp;amp;quot;Made With Xojo&amp;amp;quot;, &amp;amp;quot;&amp;amp;quot; + _
  &amp;amp;quot;00000000&amp;amp;quot; + _
  &amp;amp;quot;01011010&amp;amp;quot; + _
  &amp;amp;quot;01100100&amp;amp;quot; +_
  &amp;amp;quot;01011000&amp;amp;quot; + _
  &amp;amp;quot;01111010&amp;amp;quot; + _
  &amp;amp;quot;01000100&amp;amp;quot; + _
  &amp;amp;quot;01111010&amp;amp;quot; + _
  &amp;amp;quot;00000000&amp;amp;quot;, 8)

  ' start the loop
  Timer1.Enabled = true

Notes:
1. You’ll see I use a rather small camera size (320×240). Although this framework is heavly optimized, it is written in Xojo, not C++. You can always resize the picture after all the processing is done to get a bigger picture.

2. AddGlyphTemplate
This method adds a glyph template to our database. The GlyphValues is a string that describes the white and black values of our glyph. e.g.
Glyph
will be:

AddGlyphTemplate(cornerTemplate, “Alwaysbusy’s Corner”, “” + _
“00000” + _
“01100” + _
“00100” +_
“01010” + _
“00000”, 5)
The ‘5’ is the size of the glyph. You can create glyphs up to 23, but the bigger, the more difficult it will be to detect them.

Also beware of creating doubles! These two are exactly the same:

GlyphSame

Sub AddGlyphTemplate(PictureTemplate as Picture, Name as String, GlyphValues as String, GlyphSize as integer)
  ' check if the template is nil. If true, create a random blank one
  if PictureTemplate = nil then
    PictureTemplate = new Picture(128,128,32)
    PictureTemplate.Graphics.ForeColor = RGB(Rnd * 255, Rnd * 255, Rnd * 255)
    PictureTemplate.Graphics.FillRect 0,0,128,128
  end if
  ' make an ABXVImage from the Xojo picture
  dim arpic as new ABXVImage
  arpic.SetPicture(PictureTemplate, ABXVMASKTYPE.MASK_NONE)

  ' Create the Glyph
  dim tmpGlyph as ABXVGlyph = ABXVGlyphRecognizer.CreateGlyphTemplateFromString(Name, GlyphValues, GlyphSize)
  ' set the image to the glyph
  tmpGlyph.Image = arpic
  ' add it to the database
  GlyphDatabase.Append tmpGlyph
End Sub

And all we have to do now is following the schema above. We do this in a timer (or thread).

  ' temporary blok the loop, we're busy!
  if isBusy then Return
  isBusy = true

  ' the center of the image
  dim cx as Integer = CameraWidth/2
  dim cy as Integer = CameraHeight/2

  ' some temporary pictures
  dim BackwardQuadrilateralTransformedPicture as Picture

  ' take a picture
  #if TargetWin32
    GrabbedPicture = MyCam.GrabPicture
    ' in case no webcam was attached, use the demo picture
    if GrabbedPicture = nil then
      GrabbedPicture = new Picture(CameraWidth, CameraHeight, 32)
      GrabbedPicture.Graphics.DrawPicture DemoPicture,0,0
    end if
  #else
    ' grab the picture with your WebCam plugin
    ' for the demo, the fixed demo picture is used
    GrabbedPicture = new Picture(CameraWidth, CameraHeight, 32)
    GrabbedPicture.Graphics.DrawPicture DemoPicture,0,0
  #endif

  ' some temporaty ABVXImages
  dim GrabbedImage, GrayImage, QuadrilateralTransformedImage, BackwardQuadrilateralTransformedImage as ABXVImage

  ' convert the grabbed picture to a ABXVImage for processing
  GrabbedImage = new ABXVImage
  GrabbedImage.SetPicture(GrabbedPicture, ABXVMASKTYPE.MASK_NONE)

  ' convert to a grayscaled image. We want to keep this for later and continue on the GrabbedImage
  GrabbedImage.GrayScaleTo8Bit(ABXVGRAYSCALETYPE.GRAYSCALE_BT709)
  GrayImage = GrabbedImage.Clone

  ' run some filters
  ' first a difference edge filter
  ABXVEdgeDetector.DifferenceEdgeDetect(GrabbedImage)

  ' next change everything to black or White (2bit), using a threshold
  ABXVBinarization.Threshold(GrabbedImage, 40)

  ' lets find some blobs in our B/W picture.
  dim BlobCounter as new ABXVBlobCounter
  ' the size must minimum 32 pixels . FilterBlobs = true means filter all smaller ones out
  BlobCounter.minWidth = 32
  BlobCounter.minHeight = 32
  BlobCounter.FilterBlobs = true
  BlobCounter.FindBlobs(GrabbedImage)

  ' get the found blobs
  dim Blobs(-1) as ABXVBlob = BlobCounter.GetObjectsInformation
  ' a temporary blob to do the loop
  dim Blob as ABXVBlob

  ' temporary tables to hold the edgepoints of a blog (the border, so to day)
  dim EdgePoints(-1) as ABXVIntPoint
  dim LeftEdgePoints() as ABXVIntPoint
  dim RightEdgePoints() as ABXVIntPoint

  ' temporary table to hold the corners
  dim Corners(-1) as ABXVIntPoint

  ' Our final table that will hold all the found Glyphs
  dim FoundGlyphs(-1) as ABXVGlyph

  ' The glyph we found
  dim FoundGlyph as ABXVGlyph

  ' The Bounding Rectangle of the Found Glyph
  dim BoundingRect as ABXVRectangle

  for each Blob in Blobs
    ' find all the EdgePoints
    EdgePoints = BlobCounter.GetBlobsEdgePoints(Blob)
    ' check if the EdgePoints form a Quadrilateral, if yes, return the corners
    if ABXVShapeChecker.IsQuadrilateral(EdgePoints, Corners) then
      ' It's a Quadrilateral, let's find the left and right edges
      BlobCounter.GetBlobsLeftAndRightEdges(Blob, LeftEdgePoints, RightEdgePoints)

      ' using our gray image, we check if the difference of the edges is big enough with the surrounding pixels (B&amp;amp;lt;-&amp;amp;gt;W)
      dim Difference as Double = ABXVGlyphRecognizer.CalculateAverageEdgesBrightnessDifference(LeftEdgePoints, RightEdgePoints, GrayImage)
      if Difference &amp;amp;gt; 20 then
        ' Yes it is!

        ' Transform the found Quadrilateral to a 2D Glyph
        QuadrilateralTransformedImage = ABXVTransform.QuadrilateralTransform(grayimage, corners,GlyphSizeMM,GlyphSizeMM, true)

        ' Run a Otsu Threshold filter, returning a B/W 2Bit image
        ABXVAdaptiveBinarization.OtsuThreshold(QuadrilateralTransformedImage)

        ' look if the found Glyph matches one in our database
        FoundGlyph = ABXVGlyphRecognizer.FindGlyph(QuadrilateralTransformedImage, Corners, GlyphDatabase, GlyphSizes)
        if FoundGlyph &amp;amp;lt;&amp;amp;gt; nil then
          if FoundGlyph.RecognizedGlyph &amp;amp;lt;&amp;amp;gt; nil then
            ' yes, found and recognized!

            ' 2D augmented reality
            ' we do a Backward Quadrilateral Transformation with the picture we want to show over the glyph. A rectangle is returned where it should be placed
            BackwardQuadrilateralTransformedImage = ABXVTransform.BackwardQuadrilateralTransform(FoundGlyph.RecognizedGlyph.Image, FoundGlyph.RecognizedQuadrilateral, true, BoundingRect)
            ' translate the ABVXImage to a Xojo picture
            BackwardQuadrilateralTransformedPicture = BackwardQuadrilateralTransformedImage.GetPicture
            ' and draw it on our original picture, using the returned BoundingRect
            GrabbedPicture.Graphics.DrawPicture BackwardQuadrilateralTransformedPicture,BoundingRect.Left, BoundingRect.Top

            if Debug then
              ' if debug, draw the edges in red
              dim point as ABXVIntPoint
              for each point in edgePoints
                GrabbedPicture.Graphics.Pixel(point.x,point.y) = &amp;amp;amp;cFF0000
              next
            end if

            ' and add it to our found list to do something with it later
            FoundGlyphs.Append FoundGlyph
          end if
        end if
      end if
    end if
  next

  ' draw the result to the canvas
  canAR.Refresh

  ' ok we're done. Next!
  isBusy = false

And that’s it! In just a 100 lines of code, we created magic! Check out the help for more information on what each method does. A lot of methods contain reference links to the theory behind the function.

And what’s next? 3D Augmented Reality of course. Alwyn Bester has gracefully offered to expand the excellent X3 Core framework so that it will work very well with ABXVision.

So, a lot more fun to come in the upcoming weeks!

Download the latest framework from the ABXVision page to get version 1.0.2 with the 2D Augmented Reality demo.

If it is the first time you use ABXVision, read the Getting Started document.

Greetings,

Alwaysbusy

Click here to Donation if you like my work


RealBasic/Xojo: An ADODB Wrapper around the OLEObject framework

Note:
All problems seem to be solved for Xojo, thanks to John Hansen and Paul Lefebvre! Redownload the Wrapper at the end of the article if you had already downloaded it before.

For a project at OneTwo I needed ADODB instead of ODBC to connect to a database. Realbasic/Xojo can do this through the OLEObject, but non of the methods or properties are exposed to the programmer. If you only need it now and then that’s not a real problem, but in some cases you’ll use it all the time. So I decided to write a wrapper around the OLEObjects to make it easier. Also, all available Enums are available (ADOConstants) This way you can just program like this:

BlogArt1a

The code is also much more readable and familiar looking:

  dim CurrentPath as string
  CurrentPath = GetFolderItem("").AbsolutePath
  
  Dim conn as new ADOConnection
  conn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + CurrentPath + "Test.mdb"
  
  if conn.Open() then
    MsgBox "Database open!"
  else
    if conn.ErrorsCount > 0 then
      MsgBox str(conn.GetError(0).Number) + "- " + conn.GetError(0).Description
    end if
  end if
  
  dim SQL_str as string
  dim tmp_rs as ADORecordset
  dim ret as Boolean
  
  SQL_str = "INSERT INTO tData ( datText, datValue, datParentID ) SELECT tUren.Uur, 1 AS Expr1, 1 AS Expr2 FROM tUren ORDER BY tUren.Uur;"
  tmp_rs = conn.Execute(SQL_str)
  if tmp_rs <> nil then
    ret = tmp_rs.close
    tmp_rs = nil
    MsgBox "Records added!"
  else
    if conn.ErrorsCount > 0 then
      MsgBox str(conn.GetError(0).Number) + "- " + conn.GetError(0).Description
    end if
  end if
  
  SQL_str = "UPDATE tData SET datValue=datValue+1;"
  tmp_rs = conn.Execute(SQL_str)
  if tmp_rs <> nil then
    ret = tmp_rs.close
    tmp_rs = nil
    MsgBox "Records updated!"
  else
    if conn.ErrorsCount > 0 then
      MsgBox str(conn.GetError(0).Number) + "- " + conn.GetError(0).Description
    end if
  end if
  
  SQL_str = "SELECT datValue FROM tData WHERE datText='12:00';"
  tmp_rs = new ADORecordset
  ret = tmp_rs.Open(SQL_str, conn)
  if ret then
    MsgBox "Selected 12:00 value=" + str(tmp_rs.Field("datValue").IntegerValue)
    ret = tmp_rs.close
    tmp_rs = nil
  else
    if conn.ErrorsCount > 0 then
      MsgBox str(conn.GetError(0).Number) + "- " + conn.GetError(0).Description
    end if
  end if
  
  SQL_str = "DELETE * FROM tData;"
  tmp_rs = conn.Execute(SQL_str)
  if tmp_rs <> nil then
    ret = tmp_rs.close
    tmp_rs = nil
    MsgBox "Records deleted!"
  else
    if conn.ErrorsCount > 0 then
      MsgBox str(conn.GetError(0).Number) + "- " + conn.GetError(0).Description
    end if
  end if
  
  ret = conn.Close
  conn = nil
  
  MsgBox "Done!"

Much more like it! The framework contains the following classes:

BlogArt1b

Note:
You’ll notice that every class contains a inner… OLEObject. This is the real ADODB object, just in case I forgot something πŸ™‚

The fun part is that also objects like Stream are now available in a convenient way. You can program stuff like this with ease:

  dim objStream as new ADOStream()
  dim ret as Boolean
  ret = objStream.Open
  
  ret = objStream.WriteText("Dit is een Γ© text")
  
  objStream.Position = 0
  MsgBox objStream.ReadText + "(" + str(objStream.Size) + ")"
  
  objStream.Position = 0
  objStream.Type = StreamTypeEnum.adTypeBinary
  MsgBox  objStream.Read(2)
  
  ret = objStream.SaveToFile("c:\adodb.txt", SaveOptionsEnum.adSaveCreateOverWrite)
  
  ret = objStream.Close
  objStream = nil

Not all classes have been tested, so if you find an error please report them so I can make the needed changes for everybody.

All classes are open source and can be used for non-commercial and commercial use but you have to include the license.txt file.

Download classes + demo:
ADODBWrapper

Bye for now!


Let’s shrink the world: A Tilt Shift Effect

Everybody has seen the cute pictures floating around showing a mini world! Some examples:

But how is it done? And can it be done fast?

Answers: Easy and Yes!

How does it work?
For a digital Tilt-Shift we use some kind of gradient blur. What I mean is a blur that is very light where the focus lens is and gets blurrier the further something is in the picture. Some ‘faster’ systems only use one blur and then put the focused part back into the picture but this creates an ugly edge between the sharp and blurred part of the picture. And we do not want this!

To get his effect we could do several blurs where we enlarge the focus box in the loop so we get some gradience. Eg. If we do it 8 times we get a gradient of 8 layers. Not bad but…we want more!

Doing a blur in languages like Realstudio and Basic4Android (or Java) is not that fast. This is the first issue we’ll have to tackle because we want to be close to doing it real-time.

Second, a lot of focusses can only be a rectangle or a circle. This has a side effect: some objects in the picture will be in focus while we do not want that because it spoils the illusion. So our focus needs to be able to be irregular. In this tutorial the example uses also a rectangle but I’ll show you how to change this to anything you want.

In some cases changing the saturation of the picture can even more give the illusion of a miniature.

So in short we want:
1. few blurs, but still have 255 gradient levels
2. the blur must be fast
3. an irregular focus
4. some kind of saturation
5. did I mention fast?

How will Basic4Android (or java) and Realbasic be able to do this?

Well, we’re gonna cheat! πŸ˜€

Instead of creating a gradient blur, we are going to write one heavy blur and then resharpen it. In fact you’ll see we can do the 255 levels of sharpness with only two blurs!

In Realbasic we can take advantage of the great mask property in a picture we’ve used a lot in the past. In Basic4Android I’m going to simulate this so the code can be very similar.

For our example let’s say we want to make this:

Let me show you in a schema what the steps are we’re going to use in our Tilt-Shift (click the picture to see full size):

1. our orignal picture. Make sure it is a sharp one
2. a blurred version of (1)
3. our focus area.
4. a blurred version of (3)
5. we use our blurred (4) as a mask on our original (1). As you can see in the schema, (5) fades out to its background (the alpha channel increases), in this case white.
6. so if we draw (5) on our blurred (2) the blurred pixels will re-sharpen! Do you see the magic happening here πŸ™‚
7. the saturation is optional but may make the effect more real.

And the clever part is step 3: you can draw any shape you want here! Some examples for Realbasic:

And remember, in Basic4Android (java) it needs to be inverted:

And now for the code.
(For B4A you’ll need my ABExtDrawing library version 1.7 here)

First we’ll need a fast blur. Again we’ll cheat as we’re not doing a real gaussian blur. We are ging to exploit our mask feature again and do a diagonal blur.

We’ll need some help functions:
Resize()

Realbasic:

Function Resize(pic as picture, Percentage as integer) As Picture
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim p as Picture
  dim w,h as Integer
  dim by as Double = Percentage / 100
  
  w = pic.Width * by
  h = pic.Height * by
  
  p = NewPicture(w,h,32)
  p.Graphics.UseOldRenderer=true
  p.Graphics.DrawPicture pic,0,0,w,h, 0,0,pic.Width, pic.Height
  
  Return p
End Function

Java (B4A library):

public Bitmap Resize(Bitmap bmp, float Percentage)
{
	float by = (float) (Percentage/100.0);
	
	int w = (int) (bmp.getWidth()*by);
	int h = (int) (bmp.getHeight()*by);
	
	if (Percentage>100) {
		return Bitmap.createScaledBitmap(bmp, w, h, true);
	} else {
		return Bitmap.createScaledBitmap(bmp, w, h, false);
	}
	
}

Innerblur()
We create a mask on our picture and draw it a number of times on each other, just shifting bits to the left, top, right and bottom.

Realbasic:

Function InnerBlur(Pic as picture, Level as double) As Picture
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim p,t as Picture
  dim L,w,h as Integer
  
  w=pic.Width
  h=pic.Height
  p=NewPicture(w,h,32)
  p.Graphics.UseOldRenderer=true
  p.Graphics.DrawPicture pic,0,0
  
  t=NewPicture(w,h,32)
  t.Graphics.UseOldRenderer=true
  t.Mask.Graphics.UseOldRenderer=true
  t.Mask.Graphics.ForeColor=&c7F7F7F
  t.Mask.Graphics.FillRect 0,0,t.Width,t.Height
  
  for L=abs(Level) DownTo 1
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,-L,-L   'upper left
    
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,-L,L    'lower left
    
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,L,L     'lower right
    
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,L,-L    'upper right
  next
  
  Return p
End Function

Java (B4A library):

private Bitmap InnerBlur(Bitmap bmp, float Level) {
	int w = bmp.getWidth();
	int h = bmp.getHeight();
	
	Paint paint = new Paint();
	paint.setAlpha(0x7F);
	
	Bitmap t = Bitmap.createBitmap(w, h, Config.ARGB_8888);
	Canvas tc = new Canvas(t);
	
	Bitmap p = Bitmap.createBitmap(w, h, Config.ARGB_8888);
	Canvas pc = new Canvas(p);
	pc.drawBitmap(bmp, 0, 0, null);
	
	int Lev = (int) Level;
	
	for (int L=Lev;L>0;L--) {
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, -L, -L, null);
		
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, -L, L, null);
		
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, L, L, null);
		
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, L, -L, null);
	}
	
	t.recycle();
	return p;
}

Blur()
The actual blur function is a mix of Resizes and Innerblurs.
1. Resize to smaller
2. Innerblur to level
3. Resize back to original size
4. Innerblur with level 1 to make it smooth

Realbasic:

Function Blur(Pic as picture, Level as double, Speed as Integer) As Picture
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim p as Picture
  dim L,w,h as Integer
  
  w=pic.Width
  h=pic.Height
  p=NewPicture(w,h,32)
  p.Graphics.UseOldRenderer=true
  p.Graphics.DrawPicture pic,0,0
  
  L=Level-round(Level*.4)
  
  select case speed
  case 0
    // shrink the image, blur it
    p=Resize(p,50)
    p=InnerBlur(p, L )
    // back to normal, smooth it
    p=Resize(p, 200)
    p=InnerBlur(p, 1)
  case 1
    // shrink the image, blur it
    p=Resize(p,25)
    p=InnerBlur(p, L )
    // back to normal, smooth it
    p=Resize(p, 400)
    p=InnerBlur(p, 1)
    
  end select
  
  Return p
End Function

Java (B4A library):

/**
 * blur and image. 
 * Speed: 0 = normal, 1 = fast
 */
public Bitmap Blur(Bitmap bmp, float Level, int Speed)
{
	int L = (int) (Level - Math.round(Level*.4));
	Bitmap p = null;	 
	
	switch (Speed) {
	case 0:
		p = Resize(bmp, 50);
		p = InnerBlur(p, L);
		p = Resize(p,200);
		p = InnerBlur(p, 1);
		break;
	case 1:
		p = Resize(bmp, 25);
		p = InnerBlur(p, L);
		p = Resize(p,400);
		p = InnerBlur(p, 1);
		break;		
	}		
	return p;
}

This is, as far as I know, the fastest way to do a blur in Realbasic and Java. If someone knows of a faster one, please contact me, I’ll be very interested. πŸ™‚

Before we continue, I’m going to make a function in B4A to merge a Mask layer with a picture so we can do the tilt Shift in a way similar to Realbasic.
As mentioned in a previous article, you have to make sure your images are in a certain format on Android otherwise you can strange colors or resizes.
Here is a function that makes sure the picture is in ARGB_8888 format:

Basic4Android:

' exDraw was dimmed before as Dim ExDraw As ABExtDrawing '(version 1.70 or higher required)
Sub Convert_RGB565_To_ARGB8888(iFolder As String, iFile As String) As Bitmap	
	Dim iBmp As Bitmap
	iBmp.Initialize(iFolder, iFile)

	' only convert it if it is a RGB565
	If ExDraw.GetConfig(iBmp) = ExDraw.RGB_565 Then
		Dim w, h As Int
		
		w = iBmp.Width
		h = iBmp.Height
	
		Dim tmpBmp As Bitmap
		tmpBmp.InitializeMutable(w,h)
		Dim cnvRect As Rect
		cnvRect.Initialize(0,0,w, h)
		Dim cnvCanv As Canvas
		cnvCanv.Initialize2(tmpBmp)
		
		Dim aRect As Rect
		aRect.Initialize(0,0,w,h)
		
		cnvCanv.DrawBitmap(iBmp,Null, aRect)		
		Return tmpBmp
	Else
		Return iBmp
	End If
End Sub

And here is our merge function:

Basic4Android:

Sub MergeWithAlphaLayer(iBmp As Bitmap, iLayerBmp As Bitmap) As Bitmap
	Dim MergedBmp As Bitmap
	Dim MergedBmpCanv As Canvas
	
	Dim w, h As Int
	
	w = iBmp.Width
	h = iBmp.Height
	
	Dim aRect As Rect
	aRect.Initialize(0,0,w,h)
	
	MergedBmp.InitializeMutable(w,h)
	MergedBmpCanv.Initialize2(MergedBmp)
	
	Dim Alpha As Bitmap
	Alpha.InitializeMutable(w,h)
	
	' pic
	MergedBmpCanv.DrawBitmap(iBmp, Null, aRect)
	
	' mask
	Dim Pixels(w*h) As Int
	ExDraw.getPixels(iLayerBmp,Pixels , 0, w, 0, 0, w, h)
	Dim count As Int
	count = (w*h) - 1
	For i = 0 To count
    	    Pixels(i) = Bit.ShiftLeft(Pixels(i),8) 'move the red pixel value to the alpha channel
	Next
	ExDraw.setPixels(Alpha, Pixels, 0, w, 0, 0, w, h)
	
	Dim AlphaP As ABPaint
	AlphaP.Initialize
	AlphaP.SetAntiAlias(True)
	AlphaP.SetPorterDuffXfermode(ExDraw.PorterDuffMode_DST_IN) 'DST_IN only takes over the alpha values
	
	ExDraw.drawBitmap2(MergedBmpCanv, Alpha, 0, 0, AlphaP)
	
	ExDraw.Recycle(Alpha)
	
	Return MergedBmp
End Sub

Ok, back on track! The saturation function. I admit the one in Java has some room for improvement.

Realbasic:

Sub Saturation(Pic as picture, Ammount as integer)
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim c as color
  dim d as Double
  dim a,x,y,w,h,m() as Integer
  dim s as RGBSurface
  dim p as Picture
  dim ir,ig,ib as Integer
  
  s=pic.RGBSurface
  d=Ammount/100
  w=pic.Width-1
  h=pic.Height-1
  
  Redim m(510)
  for x=0 to 510
    m(x)=(x-255)*d
  Next
  
  for y=0 to h
    for x=0 to w
      c=s.Pixel(x,y)
      ir=c.red
      ig=c.green
      ib=c.blue
      a=( ir + ig + ib ) \ 3
      s.Pixel(x,y)=rgb( ir+m(ir-a+255) , ig+m(ig-a+255) , ib+m(ib-a+255) )
    next
  next
End Sub

Java (B4A library):

public Bitmap Saturate(Bitmap bmp,float Ammount) {
	//Initialize the ColorMatrix object  
	ColorMatrix colorMatrix = new ColorMatrix();  
	//Initialize the ColorMatrixColorFilter object  
	ColorMatrixColorFilter cmFilter = new ColorMatrixColorFilter(colorMatrix);  

	//Initialize the cmPaint  
	Paint cmPaint = new Paint();  
	//Set 'cmFilter' as the color filter of this paint  
	cmPaint.setColorFilter(cmFilter);  
	
	colorMatrix.setSaturation(Ammount/(float)100);  
	//Create a new ColorMatrixColorFilter with the recently altered colorMatrix  
	cmFilter = new ColorMatrixColorFilter(colorMatrix);  

	//Assign the ColorMatrix to the paint object again  
	cmPaint.setColorFilter(cmFilter);  

	//Draw the Bitmap into the mutable Bitmap using the canvas. Don't forget to pass the Paint as the last parameter
	Bitmap alteredBitmap = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
	Canvas canvas = new Canvas(alteredBitmap);
	canvas.drawBitmap(bmp, 0, 0, cmPaint);
	
	return alteredBitmap;
}

The actual Tilt Shift. This is nothing more that going over the steps I’ve shown in the schema.

Realbasic:

Sub DoTiltShiftRect(bmp as Picture, BlurRadius as integer, focusX as integer, focusY as integer, focusW as integer, focusH as integer, DoSaturtion as boolean)
  mBuffer = new Picture(640,480, 32)
  tmpPic = NewPicture(640,480, 32)
  tmpMask = NewPicture(640,480, 32)
  
  mBuffer = Blur(bmp,blurRadius,0)
  
  tmpPic.Graphics.DrawPicture bmp,0,0
  
  tmpMask.Graphics.FillRect(focusX,focusY - focusH/2, focusW,focusH)
  tmpMask = Blur(tmpMask, 7,1)
  
  tmpPic.Mask.Graphics.DrawPicture tmpMask,0,0
  
  mBuffer.Graphics.DrawPicture tmpPic,0,0
  
  if DoSaturtion then
    Saturation mBuffer,80
  end if
  
  Canvas1.Refresh(false)
End Sub

Basic4Android:

Sub DoTiltShiftRect(iBmp As Bitmap, BlurRadius As Int, focusR As Rect, DoSaturation As Boolean) As Bitmap
	Dim tgtCanv As Canvas
	
	Dim w, h As Int
		
	w = iBmp.Width
	h = iBmp.Height

	Dim tgtBmp As Bitmap
	tgtBmp.InitializeMutable(w, h)
	tgtCanv.Initialize2(tgtBmp)
	
	' blur the picture
	Dim srcBlurred As Bitmap
	srcBlurred = ExDraw.Blur(iBmp, BlurRadius, 0)	
	
	' create the sharp box
	Dim Alpha As Bitmap
	Alpha.InitializeMutable(w, h)
	Dim AlphaCanv As Canvas
	AlphaCanv.Initialize2(Alpha)
	' draw a full white rect on a black background
	Dim tmpR As Rect
	tmpR.Initialize(0,0,w, h)
	AlphaCanv.DrawRect(tmpR, Colors.black, True, 0dip)
	AlphaCanv.DrawRect(focusR, Colors.white, True, 0dip)
	' blur the white box to emulate a gradient blur, may be fast and ugly
	Dim AlphaBlurred As Bitmap
	AlphaBlurred = ExDraw.Blur(Alpha, 7, 1)
	
	' merge the blurred alpha layer with the bitmap into the overlay
	Dim Overlay As Bitmap
	Overlay = MergeWithAlphaLayer(iBmp, AlphaBlurred)		
	
	' saturate the picture to make it more life like
	Overlay = ExDraw.Saturate(Overlay, 80)
		
	Dim aRect As Rect
	aRect.Initialize(0,0,w,h)
	
	' draw the blurred image
	tgtCanv.DrawBitmap(srcBlurred, aRect, aRect)
	' draw the overlay
	tgtCanv.DrawBitmap(Overlay, aRect, aRect)	
	
	' saturate
	If DoSaturation Then
		tgtBmp = ExDraw.Saturate(tgtBmp, 90)
	End If
	
	ExDraw.Recycle(srcBlurred)
	ExDraw.Recycle(Alpha)
	ExDraw.Recycle(AlphaBlurred)
	ExDraw.Recycle(Overlay)
	
	Return tgtBmp
End Sub

And that’s it! Remember I’ll only did it for a rectangle here in the tutorial, but you can use anything.

Notes about the programs:

1. you can point anywhere on the y-axe in the picture to re-position the focusbox.
2. in the Realbasic version you can change the picture in the main.open() event: CurrentPicture = beach2
3. for the compiled RB version you can use the command line:

	Syntax: TiltShift.exe /PIC=picturename.jpg;focusleft;focustop;focuswidth;focusheight;dosaturation

		focus variables = the focus box
		DoSaturation: 0 = false, 1 = true

		Example:

		TiltShift.exe /PIC=beach2.jpg;30;300;580;240;1

4. in the Basic4Android version you can change the picture in the Activity_Create() sub: srcBmp = ABBitmapTools.Convert_RGB565_To_ARGB8888(File.DirAssets, "beach2.jpg")
5. the TiltShift.apk can be found in the B4A version in the folder \Objects\.

It would be nice if you give me credit if you use this code in your own program. πŸ˜‰

The full source code can be downloaded from:
Realbasic: download here
Basic4Android: download here

And some more eye candy!

Click here to Donation if you like my work


%d bloggers like this: