Vous êtes sur la page 1sur 44

F O R D E L P H I, L A Z A R U S, A N D P A S C A L

R E L A T E D L A N G U A G E S / A N D R O I D,
I O S, M A C , W I N D O W S & L I N U X
P R I N T E D, P D F, & O N L I N E V I E W

DX

BLAISE PASCAL MAGAZINE 59

MVVM DELPHI APPLICATIONS


BY OLAF MONIEN
AN ALTERNATIVE WAY FOR LOCALIZATIONS
BY PAUL NAUTA
DOCUMENT TECHNOLOGY (PDF) FOR VCL AND FMX
BY GIRISH PATIL
ARTIFICIAL INTELLGENCE PART IV
IMAGE RECOGNITION IMPROVED
BY BOIAN MITOV

PRINTED ISSUE PRICE € 15,00

DOWNLOAD ISSUE PRICE € 5,00


2 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE
BLAISE PASCAL MAGAZINE 59
D E L P H I, L A Z A R U S, S M A R T M O B I L E S T U D I O,
A N D P A S C A L R E L A T E D L A N G U A G E S
F O R A N D R O I D, I O S, M A C, W I N D O W S & L I N U X

CONTENTS
ARTICLES:

MVVM DELPHI APPLICATIONS PAGE 6


BY OLAF MONIEN
AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 13
BY PAUL NAUTA
DOCUMENT TECHNOLOGY (PDF) FOR VCL AND FMX PAGE 24
BY GIRISH PATIL
ARTIFICIAL INTELLGENCE PART IV
IMAGE RECOGNITION IMPROVED PAGE 27
BY BOIAN MITOV

Harvard researchers developed a model to design a soft robot that bends like an index
finger and twists like a thumb when powered by a single pressure source.
Credit: Harvard SEAS.
Designing a soft robot to move organically -- to bend like a finger or twist like a wrist -
- has always been a process of trial and error. Now, researchers from the Harvard John
A. Paulson School of Engineering and Applied Sciences and the Wyss Institute for
Biologically Inspired Engineering have developed a method to automatically design
soft actuators based on the desired movement.

ADVERTISERS
BARNSTEN PAGE 5
BLAISE BOOKSHOP PAGE 11
DELPHI & PASCAL EVENTS PAGE 2
DEVELOPERS EXPERTS PAGE 22
GNOSTICE PAGE 12
COMPONENTS4DEVELOPERS PAGE 44

Publisher: Foundation for Supporting the Pascal Programming Language


in collaboration with the Dutch Pascal User Group (Pascal Gebruikers Groep)
© Stichting Ondersteuning Programmeertaal Pascal

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 3


Stephen Ball Peter Bijlsma -Editor Dmitry Boyarintsev
http://delphiaball.co.uk peter @ blaisepascal.eu dmitry.living @ gmail.com
@DelphiABall

Michaël Van Canneyt, Marco Cantù David Dirkse


michael @ freepascal.org www.marcocantu.com www.davdata.nl
marco.cantu @ gmail.com E-mail: David @ davdata.nl

Benno Evers Bruno Fierens Primož Gabrijelčič


b.evers www.tmssoftware.com www.primoz @ gabrijelcic.org
@ everscustomtechnology.nl bruno.fierens @ tmssoftware.com

Fikret Hasovic Cary Jensen Peter Johnson


fhasovic @ yahoo.com www.jensendatasystems.com http://delphidabbler.com
http://caryjensen.blogspot.nl delphidabbler@gmail.com

Max Kleiner John Kuiper Wagner R. Landgraf


www.softwareschule.ch john_kuiper @ kpnmail.nl wagner @ tmssoftware.com
max @ kleiner.com

Kim Madsen Andrea Magni Boian


Kim Madsen
Mitov
kbm @ components4developers.com www.andreamagni.eu mitov
www.component4developers
@ mitov.com
andrea.magni @ gmail.com
www.andreamagni.eu/wp
Olaf MONIEN Paul Nauta PLM Solution Architect Jeremy North
olaf@developer-experts.net CyberNautics jeremy.north @ gmail.com
paul.nauta@cybernautics.nl
Detlef Overbeek - Editor in Chief Howard Page Clark Heiko Rompel
www.blaisepascal.eu hdpc @ talktalk.net info@rompelsoft.de
editor @ blaisepascal.eu

Wim Van Ingen Schenau -Editor Peter van der Sman Rik Smit
wisone @ xs4all.nl sman @ prisman.nl rik @ blaisepascal.eu
www.romplesoft.de

Bob Swart B.J. Rao Daniele Teti


www.eBob42.com contact@intricad.com www.danieleteti.it
Bob @ eBob42.com d.teti @ bittime.it

Anton Vogelaar Siegfried Zuhr


ajv @ vogelaar-electronics.com siegfried @ zuhr.nl

Editor - in - chief
Detlef D. Overbeek, Netherlands Tel.: +31 (0)30 890.66.44 / Mobile: +31 (0)6 21.23.62.68
News and Press Releases email only to editor@blaisepascal.eu

Editors
Peter Bijlsma, W. (Wim) van Ingen Schenau, Rik Smit,
Correctors
Howard Page-Clark, James D. Duff
Trademarks
All trademarks used are acknowledged as the property of their respective owners.
Caveat Whilst we endeavour to ensure that what is published in the magazine is correct, we cannot accept responsibility for any errors or omissions.
If you notice something which may be incorrect, please contact the Editor and we will publish a correction where relevant.
Subscriptions ( 2013 prices )
1: Printed version: subscription € 80.-- Incl. VAT 6 % (including code, programs and printed magazine,
10 issues per year excluding postage).
2: Electronic - non printed subscription € 50.-- Incl. VAT 21% (including code, programs and download magazine)

Subscriptions can be taken out online at www.blaisepascal.eu or by written order, or by sending an email to office@blaisepascal.eu
Subscriptions can start at any date. All issues published in the calendar year of the subscription will be sent as well.
Subscriptions run 365 days. Subscriptions will not be prolonged without notice. Receipt of payment will be sent by email.
Subscriptions can be paid by sending the payment to:
ABN AMRO Bank Account no. 44 19 60 863 or by credit card: Paypal
Name: Pro Pascal Foundation-Foundation for Supporting the Pascal Programming Language (Stichting Ondersteuning Programeertaal Pascal)
IBAN: NL82 ABNA 0441960863 BIC ABNANL2A VAT no.: 81 42 54 147 (Stichting Programmeertaal Pascal)
Subscription department Edelstenenbaan 21 / 3402 XA IJsselstein, The Netherlands / Tel.: + 31 (0) 30 890.66.44 / Mobile: + 31 (0) 6 21.23.62.68
office@blaisepascal.eu

Copyright notice
All material published in Blaise Pascal is copyright © SOPP Stichting Ondersteuning Programeertaal Pascal unless otherwise noted and may
not be copied, distributed or republished without written permission. Authors agree that code associated with their articles will be made
available to subscribers after publication by placing it on the website of the PGG for download, and that articles and code will be placed on
distributable data storage media. Use of program listings by subscribers for research and study purposes is allowed, but not for commercial
purposes. Commercial use of program listings and code is prohibited without the written permission of the author.

4 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


Get ready for 10.1 Berlin and Use the best tools in 2017
Now with free RAD Server & Bonus Pack
Use the best products for your software development, start developing with
10.1 Berlin and get ready for Linux as well.

● Offer #1:
15% discount on the Architect Version
+ a FREE RAD Server Site license (value € 5.000,--!)
+ 15 months technical support and all new versions

● Offer #2:
10% discount on the Enterprise version
+ FREE RAD Server 10 user/device license
+ 14 months technical support and all new versions

● FREE Bonus Pack (value € 899,--) with every 10.1 Berlin purchase
and also available for current 10.1 Berlin customers with an
active subscription.
The Bonus Pack contains Konopka Signature VCL Controls, Radiant Shapes,
Marco Cantù's Object Pascal Handbook and VCL & FMX premium styles.

BUY Now: https://www.barnsten.com/default/promotions

It is even more easy and fast than ever to update your VCL applications and
sell them to customers all over the world via the Windows Store.

Make use of:

● Microsoft Desktop Bridge Deployment Support


● Faster VCL development with QuickEdits
● New VCL Controls
● New Windows 10 Styles
● GetIt Installer with new items
● And much more!

ACT NOW: https://www.barnsten.com/nl/promotions


Buy RAD Studio, Delphi or C++Builder with a great discount
plus additional free software

Landing page: https://www.barnsten.com/default/promotions

5
Issue 5
Nr 1 2017 BLAISE PASCAL MAGAZINEIssue Nr 1 2017 BLAISE PASCAL MAGAZINE
MVVM DELPHI APPLICATIONS PAGE 1/5
EX PERTS
BY OLAF MONIEN
starter expert DX
Delphi & Lazarus ADVANTAGES
MVVM is a design pattern that has been This proposed design of MVVM promises a separation
of functional units (or concerns), so that the
created to help developing applications that
developer(s) can easily understand, track and alter
separate UI from business logic.
the application's behavior. It also makes it easier to
Applying this pattern to Delphi applications exchange the view with another one, without
without breaking all the comfort that Delphi is breaking the whole application – be it for multi-
renowned for is the challenge that we will take device development, reusability or for testability.
in this article. In short, by providing a stricter Separation of
Concerns (SoC) MVVM promises:
Even though the MVVM pattern has gotten a lot of
attention during the past couple of years, it is actually ● better maintainability
already about 12 years old. The pattern was first ● code that is easier to test
published in 2005 by John Gossman, a Microsoft MVP , ● better reusability
based on the works of Ken Cooper and Ted Peters, ● better support for multi-device development.
employees of Microsoft's. The pattern was created with
Microsoft's WPF/Silverlight platform in mind, but as a The latter has become more and more important,
pattern, it is not really tied to .NET or even C# or since mobile device development got into our focus.
VB.net. It can basically be applied to any UI In other words: Especially as a FireMonkey developer
application , written in any programming language. (a.k.a. multi device developer), we should check out
Not just as Delphi developer it is important to the MVVM pattern, to see how it could improve our
understand the concepts and goals of MVVM, so let's development process. Of course, even if you are
have a look at the theory, before we start coding. “just” a VCL developer , you would still be interested
to make your applications better maintainable and
Note: testable. As a side effect of strictly separating the
I'll use the term “Delphi” quite frequently in this article, View, it is basically possible to have the UI designed
but the techniques presented here apply to Lazarus / by “non-programmers”, i.e. by UI specialists.
FreePascal accordingly. The code examples will be for Delphi,
comments and feedback from Lazarus/FreePascal users is of In practice UI designers still need to run Delphi and
course welcome. probably need to understand certain Delphi basics,
but technically it would be possible to let these UI
The core idea of MVVM is to separate the View from the specialists work with just Delphi starter editions, as
Model and use a data binding mechanism to keep the long all visual components are available.
coupling between those two blocks as loose as possible.
MVVM consists of four components: DISADVANTAGES
In theory, the databinding mechanism may lead to a
1. Model higher memory and CPU usage footprint. In practice
2. View both, memory and CPU are not a big issue – even on
3. ViewModel mobile devices, as long as the application only creates
4. Data binding mechanism (Binder) and runs those views that are really needed at the
moment.
As depicted in  , the ViewModel sits in-between the View Another criticism is, that the overhead for simple
and the Model. The acronym “MVVM” has been built applications is too high, and for complex applications
top-down though (as above), which may look unrelated it might become more difficult applying the pattern.
when you first get in contact with it. Both is more a matter of experience though.
Once you get used to the
pattern and use the right
tools, even small
applications will profit
from it. Larger applications
still need enough time to be
designed properly and the
pure number of units may
overwhelm the application
architect, but effectively,
applying “the old legacy
concepts” in such an
application is fast and easy
(because we are used to it)
but may lead to chaos and
unmaintainable code
easily.

6 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


MVVM DELPHI APPLICATIONS PAGE 2/5
EX PERTS

SEPARATION OF CONCERNS This button needs to be bound to a Shipped() method


As mentioned earlier, MVVM's building block is in the ViewModel. The ViewModel in return calls a
Separation of Concerns (SoC), which means that there OrderShipped(AOrder) in the Model, passing in the
is a precise definition of ownerships and knowledge currently selected Order. The Model then executes its
that every MVVM component has. As  's arrows suggest, business logic to mark the order as shipped – maybe by
the ownership flows from left to right: updating an order record in the DBMS with a
The View only knows about the ViewModel and owns it. ShippedDate set to the current value.
In other words, the View is responsible for creating an In other words, user interaction may result in the View
instance of its ViewModel. calling into the ViewModel, which in return then calls
There may be scenarios though, where a single into the Model.
ViewModel is shared by multiple Views, typically when The View never calls directly into the model.
there is a parent View with one or more (embedded) child
views. In that case the parent View will be the owner GOALS REACHED?
Let's look at the explanations and examples above,
and all child views only know about the same (parent's)
ViewModel.
does MVVM solve the problems that it promises to?
The View does not know about the model!
Separation of Presentation and Business
The ViewModel only knows about the Model. It Logic
retrieves data/objects from the model, thus it depends Delphi (and other development tools) make it easy to
“design” an application by creating a new form in the
on the Model. It does not know about the View. In other
words: the ViewModel should not reference any controls IDE, throwing a couple of controls and DB queries on
that are part of the UI. The ViewModel is responsible to the form, start coding by implementing
expose data/objects so that the View can use OnButtonClick() events with code like
Databinding to visualize the data. Query1.Open - and voilà the nightmare begins.
MVVM helps you to separate Presentation and Business
The Model does not know about the ViewModel or the
logic, by providing a clearly structured concept of
View. All interaction with the Model is handled by the
Views, Model Views and Models.
ViewModel. The Model only contains business logic and
MVVM helps you but it cannot and will not force you.
data, which is used by the ViewModel. Presentation
You still can write cluttered code in any of the units.
logic is located in ViewModels only. There may be one or
You can reference the Model from the View – it is you
more ViewModels that share a single Model. The Models
and your team who is responsible in understanding
are created by ViewModels.
and following the concept and ideas. It is also not the
responsibility of any framework to force you to stay
Databinding is used by the View to retrieve inside the MVVM pattern, frameworks only support the
information from the ViewModel. In fact the MVVM concept by providing ready to use tools such as
pattern heavily depends on data binding to connect the DataBinding implementations.
View with its ViewModel. Even visual properties, such
as font size, colors etc. can be data bound to ViewModel
UI TESTABILITY
properties.
Unit testing (for example with DUnit ) is easily
done (yet tedious) for business logic and library code –
EXAMPLE – EXPOSING MODEL PROPERTIES
if we deal with methods with a defined environment
Let's assume the Model represents customers and their
and a well-defined set of parameters.
orders. One customer may have many orders. Orders
If the presentation logic and even the business logic is
are editable as long the order has not been shipped. The
mixed into the UI though, then things get
model might expose an property “ReadOnly” for orders,
complicated.
which internally checks if a specific order has already
There are several frameworks that claim that they can
been shipped. The actual business logic behind
“easily” automate or simulate user input and create
“Order.ReadOnly” is hidden by the Model – in a black
test suites for your whole UI. If your UI never changes
box that is.
a pixel and only uses standard controls this might
The ViewModels is now responsible to read that
work. This is not the reality though. Finding the data
ReadOnly property and expose it to the View.
to test in a UI's screen output is an extremely complex
The View is then responsible to visualize the property
and fragile task.
from the ViewModel by modifying corresponding UI
Instead of writing logic in OnButtonClick events
controls, e.g. setting Edit.readonly values or maybe
(mixing logic into the UI), MVVM helps creating
even dis-/enabling container Panels. To determine
separated methods in the ViewModel, such as the
the current value of “ReadOnly”, the View would
OrderShipped() from the example above. This
use DataBinding.
method can also be called from test units, which do
not have to deal with UI aspects. As the ViewModel
EXAMPLE – INTERACTION FROM THE VIEW
calls into the Model, which executes its logic and
Let's assume the View has a button “Order shipped” for
returns properties the get read by the ViewModel,
orders, which is clicked by a user in the shipping
such as the “ReadOnly” property in the example
department, once the box is handed over to the parcel
above, we only need to test the ViewModel.
truck.

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 7


MVVM DELPHI APPLICATIONS PAGE 3/5
EX PERTS

MULTI-DEVICE SUPPORT
As neither the Model nor the ViewModel know anything
about the View(s), and because both, presentation and
business logic is kept away from the View(s), it
becomes a realistic task to exchange a View with
another. No logic that has to be copied from one View to
the other. Just hook a View to its ViewModel and start
designing.
As a side effect, those who are working on the logic
code rarely need to touch any of the Views units, which
makes it easier to let UI specialists work on those units
exclusively.

DELPHI
How does all this theory fit into Delphi? Lets start by
looking at a typical Delphi legacy application:
Our application “OrderSystem” only has one form
(TFormMain) and a data module (TDMMain). Figure 2 : Legacy App - MainForm
The form displays a list of orders and shows some
details for the currently selected order. There is also a
button “Order shipped” , which tells the system that the
currently selected order has been handed over to the
parcel service. Once an order is shipped, the button
should be disabled and the details of an order become
readonly.
To keep things simple, TDMMain contains a
TClientDataset (QOrders) that has some order
sample data assigned in the IDE. Thus, we do not need Figure 3: Legacy App - Data Module
to go into the details of DB configuration.
As “OrderSystem” uses a TDatamodule, it is not a
totally dumb application, that puts everything, controls
and data access components (queries that is), on the
form(s). Yet, it is still a legacy application.

Figure 4 : Legacy App - Main Form Source Code

8 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


MVVM DELPHI APPLICATIONS PAGE 4/5
EX PERTS

Even though this legacy app separates out the data ViewModel
from the form and correctly connects the form through The ViewModel sits between the View and the Model. In
a TDatasource component to the data module it MVVM sample originating from .NET/C# this is usually
clearly mixes business and presentation logic into the a plain unit. We could of course use a plain unit in
user interface units: Delphi as well, but that would take away some power
There is basically no code in the data module (just some from Delphi in my opinion.
lines to simulate data persistence by reading/writing the So let's create a new data module and name it
TClientdataset's content from an XML file), all the “Main.ViewModel.pas” / “MainViewModel”.
business logic is in a ButtonShippedClick event To connect the ViewModel with the Model, use
handler. All the presentation logic code is in the two Main.Model in it, then drop a TDatasource onto it
remaining UI event handler methods. and connect it to ModelMain.QOrders. Next use
The logic code directly accesses or modifies the data in Main.ViewModel in Main.View, to provide a
the data module. connection from the view into its ViewModel.
This is a very trivial application, but clearly – even • Main.View uses Main.ViewModel
though the developer used a data module for • Main.ViewModel uses Main.Model
separation – the form is tightly bound to the data • Main.ViewModel uses a TDatasource to
module. This results in the expected issues: connect to Main.Model's data
● Hard to test Next, connect the DBGrid and the DBEdit in the View
● No easy exchange of the UI with the TDatasource in the ViewModel. The
● Logic developers have to spent Button's OnClick event in the View should look
most of their time in form units like that:
procedure TViewMain.ButtonShippedClick(
Sender: TObject);
FROM LEGACY TO MVVM begin
Let's identify MVVM components in Delphi. ViewModelMain.OrderShipped;
View end;
A View is basically a TForm in Delphi, consisting of
its DFM/XFM and PAS files. Our FormMain is the In the ViewModel, OrderShipped just gets
only view of our Orders System. routed through to the model:
To make it more clear what units relate to what procedure TViewModelMain.OrderShipped;
component in terms of MVVM, I tend to use a begin
prefixing mechanism: ModelMain.OrderShipped;
FormMainU.pas becomes “Main.View.pas”, end;
any additional form in our application would always
start with “View.”. The form itself is named The actual Business logic is then
“ViewMain”. performed in the Model:
Why not name the unit “View.Main.pas”
instead? Well, during development, you often look procedure TModelMain.OrderShipped;
for pieces that belong together: Main.View, begin
Main.ViewModel, Main.Model – with files QOrders.Edit;
named like that it is much easier in my opinion to QOrdersShipDate.Value := now;
navigate through the project manager. QOrders.Post;
Why not name the form itself “MainView” then end;
(periods are not allowed in component names)?
Next, we need an OrderIsShipped function
Well, while typing code, when you need a reference to
of type Boolean in the ViewModel,
let's say a View, then you just start typing “View…” and
then Delphi's code completion jumps in and offers
which is used from the View:
everything that starts with “View…”, which makes function TViewModelMain.OrderIsShipped: boolean;
typing very easy – at least for me. begin
result := not ModelMain.QOrdersShipDate.IsNull;
Model end;
A Model is the class or unit that holds business logic
procedure
and provides access to the database. In Delphi terms, a TViewMain.GridOrdersDrawColumnCell(Sender:
data module can serve as model. There may also be TObject; const Rect: TRect; DataCol: Integer; Column:
business classes such as TOrder, TCustomer, TColumn; State: TGridDrawState);
which would also be considered models. I will cover begin
this in a later article. // Gray backround for all already shipped orders
DMMainU.pas becomes “Main.Model.pas”, the if ViewModelMain.OrderIsShipped then
data module itself is called “MainModel”. GridOrders.Canvas.Brush.Color := clSilver;
The Model will need a method OrderShipped, GridOrders.DefaultDrawColumnCell(Rect, DataCol,
which will be called from the ViewModel. Column, State);
end;

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 9


MVVM DELPHI APPLICATIONS PAGE 5/5
EX PERTS

Finally we need a way, that the View get's notified,


when the Order data changed or at least when the
“OrderIsShipped” property changed. Remember,
the ViewModel does not know about the View, thus
must not call into the View, to trigger any updates.
For now we will chose a simple yet powerful
solution, to achieve this separation:
in ViewModel we introduce a method to register for
Oder data changes:
procedure
TViewModelMain .RegisterOrderDataChanged (
Aproc : TProc );

In DSOrders.OnDataChange we check, if anyone


registered and call the registered method if so:
procedure TViewModelMain.DSOrdersDataChange(
Sender: TObject; Field: TField);
begin
if Assigned(FOrderDataChangedProc) then
FOrderDataChangedProc();
end;

The View registers during its OnCreate event:


procedure TViewMain.FormCreate(Sender: TObject);
begin
ViewModelMain.RegisterOrderDataChanged(
procedure
begin
EditParcelService.ReadOnly := ViewModelMain.OrderIsShipped;
ButtonShipped.Enabled := not ViewModelMain.OrderIsShipped;
end);
end;

CONCLUSION
In the demo above we created a Delphi MVVM application that follows the concepts of MVVM
without breaking the comfort that Delphi provides as a RAD tool. We only have seen a very
simple application, which serves well as a starter though. It clearly demonstrates:

● The View only contains code to “paint” controls, depending on states in its ViewModel
● The ViewModel is strictly separated from the View
● The ViewModel only contains presentation properties and presentaion logic
● The Model is strictly separated from the ViewModel and View
● The Model only contains business logic and data access.

In the next article, I will introduce some more advanced topics such as how to use business
classes like TCustomer and Torder.

There is also an alternative of how Order data change notification can be sent:
Currently, just one receiver (the Main View in the demo) can be notified of order data changes,
by registering an OrderDataChange method. We could certainly implement some sort of
container, that would be able to handle more than just one registration, but this might become
more complex than expected.
The alternative is to use a “global message center” that distributes notifications from multiple
senders to multiple receivers. This is a technique, that I will present in the next article as well.

10 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


OFFER:
GET THE BOOK INCLUDING
THE NEW LIBRARY STICK
R K S E
DI FOR 50 EUROS + SHIPPING

AVID
l
es.htm
Gam
DAVID DIRKSE COMPUTER MATH & GAMES IN PASCAL

ath_
puterM

D ales
at
pres laisepas
.b
cal.e
u/Da
vidD
irkse
/Com

INCLUDING:
www
• 53 projects and code fully explained.
HOWARD PAGE-CLARK

The Delphi 3 – Delphi Berlin source code


is available together with full explanation.
Most of the projects can be done with
FPC and or Lazarus as well.
• For a preview go to:
http://www.blaisepascal.eu/david_dirkse/
UK_Book_Department_DavData.html
• PDF file - excellent readable on your
dure
; tablet. Printable.
proce
var 9 do
begin := 1 to
for i
begin

end;
end;
L I B R A R Y 2 0 1 6

TH & 3 4
1 2 5
MA
UTER PASCAL
6 10 12
7 8 9 11
M P 13 18 19
CO S IN 14 15 16 17 27
ME 20 25 26
GA 28
21
22 23 24
34
35 36 37
29 30 31+ 33
32
32 40 41 42
38 39 45+ 43
POCKET EDITION 46 47 48 49
44
Also printed in full color. 50 53
51 52 54+
A fully indexed PDF file is included. 55
Resize, rotate, compress digital images.
Design your own font, generate and reduce
Truth Tables from Boolean algebra.
And more important: understand how it all
works!
The book contains 87 chapters, 53 projects
with source code and compiled programs
(exe). The book is highly educational and
suitable for beginners as well as for WINDOWS 10

professionals. P lay board games, solve


puzzles, operate a vintage mechanical BLAISE PASCAL MAGAZINE
AUTHORS ALFABETICAL ALL ISSUES IN ONE FILE
calculator, Produce 3-dimensional computer
art, generate lists of prime numbers, explore PUBLISHER: PRO PASCAL FOUNDATION
and draw any mathematical function.
Solve systems of equations, calculate the
area of complex polygons. THE FAMOUS LIBRARY STICK
Draw lines, circles and ellipses. Newest technique USB 3 / 16 GB
For the games, winning strategies are you receive the library of the magazine updated
explained. For puzzles the search algorithm. from issue 1 to issue 55. See details at
For all projects: the math behind is thoroughly http://www.blaisepascal.eu/subscribers/
discussed. UK/UK_CD_DVD_USB_Department.html

BOOK INCLUDING THE LIBRARY STICK EXCL. SHIPPING


50€
http://www.blaisepascal.eu/subscribers/UK/UK_CD_DVD_USB_Department.html
Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 11
Gnostice Smart needs...Smarter solutions...
TM

Overview
Gnostice XtremeDocumentStudio Delphi is Use a single viewer control to Export reports from ACE
the next-generation multi-format display multiple formats - Reporter, FastReport,
document-processing component suite for documents (DOCX and PDF) QuickReport and
Delphi/C++Builder developers. and images (BMP, JPEG, PNG, ReportBuilder to PDF,
XtremeDocumentStudio includes VCL WMF, EMF and TIFF) PDF/A, XLSX, RTF, HTML,
components for viewing, printing, and XHTML, TXT, SVG, PNG,
converting PDF, DOCX, DOC, RTF, BMP, Display & print DOCX - Support JPEG, GIF and othe formats.
JPEG, PNG, WMF, EMF, and single-page for pages, sections, headers,
and multi-page TIFF. It also has report- footers, columns, paragraphs, Report-export PDF/A support
export components for ACE Reporter, images, text wrapping around includes PDF/A 1b, 2b, and 3b
FastReport, QuickReport and images… versions. PDF/A-3b support
ReportBuilder components that can export complies with ZUGFeRD
to PDF, PDF/A, XLSX, RTF, HTML, XHTML, Display & print PDF - Text, electronic invoicing standard.
TXT, SVG, PNG, JPEG and GIF formats. images, shapes
PDF/A support includes compliance with 100% Delphi Pascal code
PDF/A 1b, 2b, and 3b versions. PDF/A-3b Multi-format FireMonkey
support complies with ZUGFeRD electronic document viewer Support for files, streams and
invoicing standard. In future, creation and DB blobs
editing support will be added. Advanced printing of DOCX,
PDF, BMP, JPEG, PNG, WMF, Unicode text-rendering support
XtremeDocumentStudio also includes EMF and TIFF files
FireMonkey support. Its FireMonkey 12 months free upgrades
document viewer can display PDF, DOC, Conversion support for PDF-to- (major and minor) and priority
RTF and images on Windows, Mac, iOS and RTF, PDF-to-text, PDF-to- e-mail support
Android platforms. The FireMonkey image, DOCX-to-PDF, DOCX-to-
support is currently in beta and we are image, image-to-image exports
continually improving the rendering with
support for more and more features of
each of the formats.
COUPON CODE. “XDOCBP” FOR 25% DISCOUNT ON
XtremeDocumentStudio Delphi is written
XTREMEDOCUMENTSTUDIO DELPHI ULTIMATE.
in 100% Object Pascal for both VCL and
FireMonkey. It can process all supported VALID UNTIL FEB 25, 2017.
formats without requiring external
software such as Microsoft Word, Open
XML SDK, Adobe PDF library or
GhostScript.

All Gnostice Delphi/C++Builder developer Users can go to the Buy Now page at:
tools including XtremeDocumentStudio https://www.gnostice.com/xdoc-delphi
Delphi are available in the highly- and use this to purchase
discounted XtremeDevSystem Delphi XtremeDocumentStudio Delphi Ultimate.
Subscription bouquet.
AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 1/9
BY PAUL NAUTA
starter expert
This way of working is quite ok for library functions
and has following advantages:
ABSTRACT
● The language can be changed on the fly,
Localization is the process to adapt your application
you would just need to give ProgramLanguage
to different languages. The standard way of doing
a different value
this in Delphi is via language specific resource files.
● The text strings are defined at the location
This article describes an alternative way for
where they are being used
localization, by using a TClientDataSet to store the
language specific resource strings. Pros and cons of ● A new application using existing library
this approach will be shown. functions would not need to arrange anything
additional for localization of those
library functions
INTRODUCTION But it also has following disadvantages:
The standard way of localization via resource files and ● Addition of an extra language requires an
resource DLL's has several drawbacks: additional set of strings, each stored in the
● Many new files must be created application
● Resource DLL's require one additional project ● With more languages, the IF…THEN statements
per language become rather extensive
● Synchronization across all supported
languages can be quite some work, in many This led me to the approach to store this
different files information in a TClientDataSet.
● Maintenance in case of changes to the
application is in principle straight forward USING TCLIENTDATASET
but in practice very cumbersome and often To store language strings in a DataSet we need a field
only partly executed for every language and an Identifier for the string we
Being a Dutchman, I have projects in Dutch and want to translate.
English, but only a very few projects are both in Dutch
and English. They all share quite some generic libraries This Identifier has some special requirements:
and component libraries so localization of them is ● It must be understandable from the context
important to reduce design work load. To avoid setting of the code where it is being used. Therefore,
up language DLL's for every project, also for the it should be meaningful and thus not a
projects that will never be used in a different language, meaningless number.
I designed a simple set of language options, a global ● It must be unique, to be able to find the correct
variable containing the selected language and several language string. And because we are a little bit lazy,
text strings for each of the languages. Let us look at a it should be case insensitive
simple function to check if a file exists and to report
that to the user: We foresee that different units might access the same
Identifier with a slightly different meaning so it is
useful to extend the unique key with an
TYPE identifier for the unit in which the string
TPKN_Language = ( lanENG, lanNLD ); is used. This will also help identifying
VAR ProgramLanguage : TPKN_Language;
which strings must be changed in case the
FUNCTION CheckFileExists( FileName : String ) : Boolean; application must be changed.
VAR sMessage : String; To access the DataSet we need of course
BEGIN some special functions to set the language
Result := FileExists( FileName ); specific text strings and to read them,
IF Result THEN plus a facility to specify the language.
BEGIN As we might need more dedicated
sMessage := 'File already exists: ' + FileName; functionality in future we will define
IF ( ProgramLanguage = lanNLD ) THEN a derived component to arrange all this:
sMessage := 'Bestand bestaat al: ' + FileName;
END
ELSE
BEGIN
sMessage := 'File does not exist: ' + FileName;
IF ( ProgramLanguage = lanNLD ) THEN
sMessage := 'Bestand bestaat niet: ' + FileName;
END;
ShowMessage( sMessage );
END;

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 13


AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 2/9

TPKN_Localization = CLASS( TClientDataSet )


PRIVATE
FLanguage : TPKN_Language;
FLanguageTxt : String;
PROTECTED
PROCEDURE DefineDataSet;
PROCEDURE SetLanguage( Value : TPKN_Language );
PUBLIC
CONSTRUCTOR Create( AOwner : TComponent ); OVERRIDE;
FUNCTION GetLanguageText( UnitKey : String; Name : String ) : String;
PROCEDURE SetLanguageText( UnitKey : String; Name : String; LanguageText : String;
ALanguage : TPKN_Language; OverWrite : Boolean );
PROPERTY Language : TPKN_Language READ FLanguage WRITE SetLanguage;
PROPERTY LanguageTxt : String READ FLanguageTxt;
END;

You might notice that I prefix all my own types with my initials, thus TPKN_*. Let us start with
explaining the Constructor, which is implemented as:
CONSTRUCTOR TPKN_Localization.Create( AOwner : TComponent );
VAR sDirectory : String;
BEGIN
INHERITED;
sDirectory := ExtractFilePath( Application.ExeName );
FileName := sDirectory + 'Localizations.cds';
IF NOT FileExists( FileName ) THEN DefineDataSet;

Open;
IndexDefs.Add( 'Identifier', 'LOC_UNITID;LOC_NAME', [ ixUnique, ixCaseInsensitive ] );
IndexName := 'Identifier';
FLanguage := lanENG;
FLanguageTxt := GetEnumName( TypeInfo( TPKN_Language ), Ord( FLanguage ) );
END;

What is this Constructor doing? It defines the DataSet to use as 'Localizations.cds', located in the same
directory as the application. If this file is not yet present, the application will automatically create it via the
DefineDataSet procedure. Because the saved TClientDataSet does not save the index, we will create the index in
the Constructor; it will be needed to find the required language strings. Finally we define the initial language to use as
lanENG. For easy reference, we store the string representation of the language in FLanguageTxt.
The DefineDataSet determines the full field definition of the DataSet and is implemented as follows:
PROCEDURE TPKN_Localization.DefineDataSet;
VAR iLanguage : TPKN_Language; sLanguage : String;
BEGIN
FieldDefs.Clear;
FieldDefs.Add( 'LOC_ID', ftAutoInc );
FieldDefs.Add( 'LOC_UNIT_ID', ftString, 40 );
FieldDefs.Add( 'LOC_NAME', ftString, 30 );
FOR iLanguage := Low( TPKN_Language ) TO High( TPKN_Language ) DO
BEGIN
sLanguage := GetEnumName( TypeInfo( TPKN_Language ), Ord( iLanguage ) );
FieldDefs.Add( 'LOC_TEXT_' + UpperCase( sLanguage ), ftString, 100 );
END;
CreateFields;
CreateDataset;
SaveToFile;
END;

Thus, we create 'LOC_ID' as an ftAutoInc field as the unique record identifier and 'LOC_NAME' as string for the Text
Identifier. The UnitID will be stored in the string field 'LOC_UNIT_ID'. After this we create a field for every language
that is defined in TPKN_Language, using its string representation. So, texts for the language lanNLD will be stored in
the field 'LOC_TEXT_LANNLD', etc. The 'CreateFields' procedure actually creates the fields in the
TClientDataSet, while CreateDataSet will create the DataSet in memory. Via SaveToFile the DataSet
will be saved to the file 'Localizations.cds'. Thus, the developer has to do nothing whenever he implements an instance
of TPKN_Localization, this file will be created automatically if not yet existing.
The SetLanguage procedure is rather straight forward:

14 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 3/9

PROCEDURE TPKN_Localization.SetLanguage( Value : TPKN_Language );


VAR sLanguage : String;
BEGIN
IF ( FLanguage <> Value) THEN
BEGIN
sLanguage := GetEnumName( TypeInfo( TPKN_Language ), Ord( Value ) );

IF ( Fields.FindField( 'LOC_TEXT_' + sLanguage ) <> NIL ) THEN


BEGIN
FLanguage := Value;
FLanguageTxt := sLanguage;
END
ELSE
BEGIN
RAISE Exception.Create( 'Language is not supported: ' + sLanguage );
END;
END;
END;

The most interesting part is the Exception. Why would it be needed? Well, we could have a situation that after
creation of 'Localizations.cds' we add a new language to TPKN_Language for which no corresponding field
is present yet, so we need to signal it. It is not straight forward to change the field definition of an already existing
TClientDataSet (there is no functionality like RestructureTable) so we leave that out of scope for this demonstration.
Another remark can be made on the error message itself: here we need hard coding of the text string because when
there are errors in this component we will probably not be able to show a language specific message.
This is thus a draw-back, although only a minor one.

ACCESSING THE LANGUAGE STRINGS


The DataSet is now present but still empty. We could fill it via some Editor for TClientDataSet, but that would
lead to a very loose coupling with the application, so we will use the SetLanguageText procedure:
PROCEDURE TPKN_Localization.SetLanguageText( UnitKey : String; Name : String; LanguageText : String;
ALanguage : TPKN_Language; OverWrite : Boolean );
VAR iLanguage : TPKN_Language;
BEGIN
IF ( LanguageText = '' ) THEN Exit;
iLanguage := FLanguage;
TRY
TRY
SetLanguage( ALanguage );
SetKey;
FieldByName( 'LOC_UNIT_ID' ).AsString := UnitKey;
FieldByName( 'LOC_NAME' ).AsString := Name;
IF NOT GotoKey THEN
BEGIN
Append;
FieldByName( 'LOC_UNIT_ID' ).AsString := UnitKey;
FieldByName( 'LOC_NAME' ).AsString := Name;
END
ELSE IF OverWrite OR ( FieldByName( 'LOC_TEXT_' + FLanguageTxt ).AsString = '' ) THEN
Edit
ELSE
Exit;

FieldByName( 'LOC_TEXT_' + FLanguageTxt ).AsString := LanguageText;


Post;
SaveToFile;
EXCEPT
ON E: Exception DO
ShowMessage( 'Error storing Language Text for Unit ' + UnitKey + ', Name: ' + Name );
END;
FINALLY
SetLanguage( iLanguage )
END;
END;

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 15


AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 4/9

The UnitKey parameter is simply the unique identifier for the Unit that calls this procedure, while the reference to
the Text String is given via the Name parameter. The actual string to store is given via the parameter
LanguageText. Of course, we need to specify here also the language for which LanguageText is valid, via the
ALanguage parameter. Finally, the OverWrite parameter is used to define if the value will always be set
(Overwrite = True) or only in case not yet set (thus empty). In the procedure above we use iLanguage to store
the current value of the language setting for later reset and SetLanguage to set the desired language, including the
exception handling. The procedure checks if the required record is already present. If not, the record is created and
the string value is set, otherwise the value is set but only in case OverWrite is True.
To retrieve a language specific text value, we use the GetLanguageText procedure:

FUNCTION TPKN_Localization.GetLanguageText( UnitKey : String; Name : String ) : String;


VAR sLanguage : String;
BEGIN
SetKey;
FieldByName( 'LOC_UNIT_ID' ).AsString := UnitKey;
FieldByName( 'LOC_NAME' ).AsString := Name;

IF NOT GotoKey THEN Result := ''


ELSE
BEGIN
Result := FieldByName( 'LOC_TEXT_' + FLanguageTxt ).AsString;
IF ( Result = '' ) THEN
BEGIN
sLanguage := GetEnumName( TypeInfo( TPKN_Language ), 0 );
Result := FieldByName( 'LOC_TEXT_' + sLanguage ).AsString;
END;
END;
END;

This implementation is also rather straight forward, but with some special remarks: in case the Identifier is not
found, it will return an empty value (of course you could define a different solution, for example an exception or an
'Undetermined' value). In case the record is found but the language text is empty, it will return the first language in
the TPKN_Language type, which will normally be the base language in which applications are developed.
Also in this case, we could issue warnings or exceptions, but in many situations it is better to show a base language
than annoying warnings to users.
PRACTICAL EXAMPLE
In view of the situation that we need to retrieve language texts frequently, it is the best approach to create an in
instance of TPKN_Localization only once, during initialization. It is maybe not desired to expose this
DataSet directly to the application; it could lead to unwanted usage, so we resolve this via wrappers for the real
functionality:
IMPLEMENTATION
tabLocalizations : TPKN_Localization;
FUNCTION GetLanguage : String;
BEGIN
Result := tabLocalizations.Language;
END;

FUNCTION GetLanguageText( UnitKey : String; Name : String; Variable1 : String = '';


BEGIN
Result := tabLocalizations.GetLanguageText( UnitKey, Name );
Result := StringReplace( Result, '%1', Variable1, [ rfReplaceAll ] );
Result := StringReplace( Result, '%2', Variable2, [ rfReplaceAll ] );
Result := StringReplace( Result, '%3', Variable2, [ rfReplaceAll ] );
END;
PROCEDURE SetLanguage( Language : TPKN_Language );
BEGIN
tabLocalizations.SetLanguage( Language );
END;
PROCEDURE SetLanguageText( UnitKey : String; Name : String; LanguageText : String; Language : TPKN_Language );
BEGIN
tabLocalizations.SetLanguageText( UnitKey, Name, LanguageText, Language, True );
END;

16 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 5/9
PROCEDURE SetLanguageTextDefault( UnitKey : String; Name : String; LanguageText : String;
Language : TPKN_Language );
BEGIN
tabLocalizations.SetLanguageText( UnitKey, Name, LanguageText, Language, False );
END;
INITIALIZATION
tabLocalizations := TPKN_Localization.Create( NIL);
FINALIZATION
IF Assigned( tabLocalizations ) THEN
FreeAndNil( tabLocalizations );

In this way, the DataSet will become a local object: tabLocalizations, only accessible via the wrapper functions.
The GetLanguageText function has a number of extra parameters to allow replacing %1, %2 or %3 parameters
by values from the actual context. This is needed because different languages could place such context on different
positions in the text. The SetLanguageText function always sets a value, the SetLanguageTextDefault only
when not yet present.
Now we can rewrite our original code for CheckFileExists to:
FUNCTION CheckFileExists( FileName : String ) : Boolean;
VAR sMessage : String;
BEGIN
Result := FileExists( FileName );

IF Result THEN
sMessage := GetLanguageText( UnitID, 'FileAlreadyExists', FileName )
ELSE
sMessage := GetLanguageText( UnitID, 'FileDoesNotExist', FileName );

ShowMessage( sMessage );
END;

In this case we use only one parameter: FileName, to specialize the text to show to the user.
But we are not yet ready: we need to have a value for UnitID:

CONST
UnitID = '{023F4EF2-0690-4723-865B-3537DCE19B32}'

Here we use a GUID which is truly unique, even across all computers in the world! You can create such GUID
in the IDE via CTRL-SHIFT-G. We could also use the Name of the Unit for this constant but in my experience,
it sometimes happens that the same unit name is used in a different project with a different meaning (most
infamous are the Unit1.pas filenames that are generated automatically by Delphi for new Units).
However, the function would not yet show a decent message, because we still need to define the proper
language texts:
INITIALIZATION
SetLanguageTextDefault( UnitID, 'FileAlreadyExists', 'File "%1" already exists', lanENG );
SetLanguageTextDefault( UnitID, 'FileAlreadyExists', 'Bestand "%1" bestaat al', lanNLD );
SetLanguageTextDefault( UnitID, 'FileDoesNotExist', 'File "%1" does not exist', lanENG );
SetLanguageTextDefault( UnitID, 'FileDoesNotExist', 'Bestand "%1" bestaat niet', lanNLD );

Here we define the texts both for English and Dutch. It is done in the initialization section, thus at start-up of
the application, to have the performance penalty only once.
The coupling between 'FileAlreadyExists' in SetLanguageTextDefault and GetLanguageText is
rather weak, we could make it stronger be defining a constant like:
CONST
lcFileAlreadyExists = 'FileAlreadyExists';
which could change the code to:
sMessage := GetLanguageText( UnitID, lcFileAlreadyExists, FileName )
and
SetLanguageTextDefault( UnitID, lcFileAlreadyExists, 'File "%1" already exists', lanENG );

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 17


AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 6/9

Any typing error would now lead to a compiler error. In fact, this is quite similar to using ResourceStrings
although the implementation is completely different.

EXTENSION FOR LABELS ON FORMS


We do not only need special language strings for all kind of (error) messages but also for labels on forms. Delphi
solves this by separate form files for each language, but that can also be arranged with the
TPKN_Localization as I will show. For this we need a procedure that evaluates the components on a form
and checks if language texts have been defined for properties like Caption.
So, we extend TPKN_Localization by:
PROCEDURE TPKN_Localization.SetFormLanguage( Form : TForm; UnitKey : String );
BEGIN
SetKey;
FieldByName( 'LOC_UNIT_ID' ).AsString := UnitKey;
GotoNearest;
IF ( FieldByName( 'LOC_UNIT_ID' ).AsString <> UnitKey ) THEN Exit;

SetComponentLanguage( Form, UnitKey );


END;

First we check if the Form has any items in the Localizations DataSet. If not, we can stop right-away.
Next we will analyze all components on the form to see if text strings must be adapted:
PROCEDURE TPKN_Localization.SetComponentLanguage( Component : TComponent; UnitKey : String );
VAR iComponent : Integer; sText : String; oMenu : Tmenu; iMenu : Integer;
BEGIN
IF HasProperty( Component, 'Caption' ) THEN
BEGIN
sText := GetLanguageText( UnitKey, Component.Name + '.Caption' );
IF ( sText <> '' ) THEN
SetPropValue( Component, 'Caption', sText );
END;
FOR iComponent := 0 TO Component.ComponentCount - 1 DO
IF ( Component.Components[ iComponent ] IS TMenu ) THEN
BEGIN
oMenu := TMenu( Component.Components[ iComponent ] );
FOR iMenu := 0 TO oMenu.Items.Count - 1 DO
SetMenuLanguage( oMenu.Items[ iMenu ], UnitKey );
END
ELSE
SetComponentLanguage( Component.Components[ iComponent ], UnitKey );
END;

Remember that a form is a component, which owns other components. And these components could
own components as well (like TPanel or TGroupBox). So we need recursive calling of this procedure,
that is the reason why TComponent is given as Parameter and not TForm. Because not every
component has a Caption (for example, TEdit has no Caption), we need to find out if the
component has a Caption. This is done by adding the HasProperty function which looks like:
FUNCTION HasProperty( Comp : TComponent; CONST Prop : String ) : Boolean;
BEGIN
Result := ( GetPropInfo( Comp.ClassInfo, Prop ) <> NIL );
END;

You will need to add System.TypInfo to the USES section to make it functional.
Menus are also components, but their menu items cannot be obtained via the Components property
but only via the Items property. In that case, the procedure SetMenuLanguage must be used:
PROCEDURE TPKN_Localization.SetMenuLanguage( MenuItem : TMenuItem; UnitKey : String );
VAR
sText : String;
iMenu : Integer;
BEGIN
sText := GetLanguageText( UnitKey, MenuItem.Name + '.Caption' );
IF ( sText <> '' ) THEN
MenuItem.Caption := sText;

FOR iMenu := 0 TO MenuItem.Count - 1 DO


SetMenuLanguage( MenuItem.Items[ iMenu ], UnitKey );
END;
Issue Nr 10 2016 BLAISE PASCAL MAGAZINE
AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 7/9

We do not want to expose all these new procedures to the application; in fact,
we only need a wrapper for SetFormLanguage:
PROCEDURE SetFormLanguage( Form : TForm; UnitKey : String );
BEGIN
tabLocalizations.SetFormLanguage( Form, UnitKey );
END;

DEMO APPLICATION
To see this at work, we create a demo application with a TGroupBox, 2 TButtons, a TComboBox and a TMemo.
We also need a Main Menu and a PopupMenu for the TMemo:

The mnuMain gets 2 top items (mnuTest1 and mnuTest2). The mnuTest2 gets a subitem mnuTest21 and
a subsubitem mnuTest211. The mnuPopup gets 2 items, mnuPopup1 and mnuPopup2. We will set the
default language specific text values as follows:
INITIALIZATION
SetLanguageTextDefault( UnitID, 'frmMain.Caption', 'Localization Demo', lanENG );
SetLanguageTextDefault( UnitID, 'frmMain.Caption', 'Localisatie Demonstratie', lanNLD );
SetLanguageTextDefault( UnitID, 'btnButton1.Caption', 'Check File Exists', lanENG );
SetLanguageTextDefault( UnitID, 'btnButton1.Caption', 'Controleer of Bestand bestaat', lanNLD );
SetLanguageTextDefault( UnitID, 'btnButton2.Caption', 'Change Language', lanENG );
SetLanguageTextDefault( UnitID, 'btnButton2.Caption', 'Verander van Taal', lanNLD );
SetLanguageTextDefault( UnitID, 'mnuTest1.Caption', 'Execute', lanENG );
SetLanguageTextDefault( UnitID, 'mnuTest1.Caption', 'Uitvoeren', lanNLD );
SetLanguageTextDefault( UnitID, 'mnuTest2.Caption', 'Modify', lanENG );
SetLanguageTextDefault( UnitID, 'mnuTest2.Caption', 'Wijzigen', lanNLD );
SetLanguageTextDefault( UnitID, 'mnuTest21.Caption', 'Hide', lanENG );
SetLanguageTextDefault( UnitID, 'mnuTest21.Caption', 'Verbergen', lanNLD );
SetLanguageTextDefault( UnitID, 'mnuTest211.Caption', 'Remove', lanENG );
SetLanguageTextDefault( UnitID, 'mnuTest211.Caption', 'Verwijderen', lanNLD );
SetLanguageTextDefault( UnitID, 'mnuPopup1.Caption', 'Find', lanENG );
SetLanguageTextDefault( UnitID, 'mnuPopup1.Caption', 'Zoeken', lanNLD );
SetLanguageTextDefault( UnitID, 'mnuPopup2.Caption', 'Replace', lanENG );
SetLanguageTextDefault( UnitID, 'mnuPopup2.Caption', 'Vervangen', lanNLD );
SetLanguageTextDefault( UnitID, 'cbxCombo.Items.Text', 'One;Two;Three', lanENG );
SetLanguageTextDefault( UnitID, 'cbxCombo.Items.Text', 'Een;Twee;Drie', lanNLD );
SetLanguageTextDefault( UnitID, 'memMemo.Lines.Text', 'This is a Memo;In English', lanENG );
SetLanguageTextDefault( UnitID, 'memMemo.Lines.Text', 'Dit is een Memo;In het Nederlands', lanNLD );
SetLanguageTextDefault( UnitID, 'gbxGroupBox.Caption', 'File Group Box', lanENG );
SetLanguageTextDefault( UnitID, 'gbxGroupBox.Caption', 'Bestands Groep', lanNLD );

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 19


AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 8/9

To replace the text as defined in the dfm file by these settings, we need to call SetFormLanguage on
Activation of the form. You might notice the settings for memMemo.Lines.Text and
cbxCombo.Items.Text. The Text property does not exist on these components, in fact these components
have Lines and Items respectively as property, which are TStrings. In the current implementation of
SetComponentLanguage this cannot be translated so we need a workaround in the Activation:
PROCEDURE TfrmMain.FormActivate( Sender : TObject );
BEGIN
SetFormLanguage( Self, UnitID );
cbxCombo.Items.Text := StringReplace( GetLanguageText( UnitID, 'cbxCombo.Items.Text' ),
';', #13#10, [ rfReplaceAll ] );
cbxCombo.Text := cbxCombo.Items[ 0 ];
memMemo.Lines.Text := StringReplace( GetLanguageText( UnitID, 'memMemo.Lines.Text' ),
';', #13#10, [ rfReplaceAll ] );
END;

Please note that we need to replace the semi-colon by CarriageReturn/LineFeed to populate the
properties properly. Also, we set the initial value of the ComboBox to the first value in the Items list.
With the first button, we check if a file exists. A simple implementation could look like:
PROCEDURE TfrmMain.btnButton1Click( Sender : TObject );
BEGIN
CheckFileExists( 'C:\Windows\System32\Calc.exe' );
END;

To change the language, we will use the second button:


PROCEDURE TfrmMain.btnButton2Click( Sender : TObject );
BEGIN
IF ( GetLanguage = lanENG ) THEN
SetLanguage( lanNLD )
ELSE
SetLanguage( lanENG );

FormActivate( Sender );
END;

First we set the new required language, then we simply call again FormActivate to
actually change the user interface. Starting the application will show:

20 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


AN ALTERNATIVE WAY FOR LOCALIZATIONS PAGE 9/9

After pressing the Change Language button, it will look like:

Simple and straight forward, isn't it?


Everything is stored in the Localizations DataSet so let us have a look at its content:

CONCLUSION
I have shown that it is very well possible to execute localizations by using a TClientDataSet.
Especially for libraries and generic components this is quite simple. Whenever they are used, localization is
already automatically present without any additional action for the developer, but the localization could be
adapted using an Editor for Localizations.cds. Extra languages could easily be added to
TPKN_Language after which the necessary strings can be added via an Editor. I would recommend to use
SetLanguageTextDefault only for one or two base languages and add other languages via the Editor.
It could even be considered to allow end users to modify text themselves via such an Editor.
The Editor also gives a nice overview of all text strings, for all languages, in one place.
Very convenient for maintenance and standardization!
However, this approach has some drawbacks:
· Complex forms cannot easily be handled in the described way ( it would require rather complex
extensions to SetComponentLanguage with inherent performance penalty)
· Startup of the application will take longer
· Showing a screen will take longer
· The size of the application becomes somewhat larger
· Using huge amounts of text translations and many languages could have impact on the memory
consumption of TClientDataSet
I have the opinion that only the situation of complex forms is a serious one. The others will hardly be
noticeable. Fortunately, there is no need to choose either between the standard Delphi way or using this
approach: they can exist together and even supplement each other!

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 21


Experts
DOCUMENT TECHNOLOGY FOR VCL AND FMX PAGE 1/3
BY GIRISH PATIL Gnostice TM

Smart needs...Smarter solutions...


starter expert DX
Embedding PDF and Word document viewing in
Delphi
your VCL and FMX applications using
Processing and viewing PDF, Office formats and XtremeDocumentStudio is really simple. Here are
other types of documents is an increasingly common the general steps:
need for any business application. In these series of • Place a TgtDocumentViewer component on
articles we will look at various aspects of different the VCL or FMX form of your application
document formats and examples of implementing where you want to show the viewer.
document functionality in your applications. For the • Build the toolbar and link up the toolbar
examples of implementations, we will be using the buttons to instances of the included
Gnostice XtremeDocumentStudio Delphi product. standard actions.
• Change the initial state of the display if
required by setting the Display settings
In this first issue we will take an introductory look at properties of the viewer.
XtremeDocumentStudio Delphi's new document
viewing capabilities. That's all you need to do. You are now ready to run
t includes a PDF and Word document viewer your application and view PDF and Word documents
component for VCL and FMX applications. in your VCL and FMX applications
XtremeDocumentStudio provides a document viewer
component named TgtDocumentViewer.
This document viewer is a unified viewer for several
formats. It supports uniform display capabilities across
formats and exposes a uniform set of APIs to operate on
the formats. The APIs and capabilities are uniform
across the VCL and FMX versions of the components.
The formats currently supported are PDF, DOCX, DOC,
RTF(under VCL), and various image formats.

The Document Viewer is designed to provide flexibility


in implementing a viewer according to the applications
look and feel and viewing needs. It also provides a wide
set of viewing and display options that are configurable
through properties. Furthermore,
XtremeDocumentStudio is a framework that provides
programmable end-points that allow developers to
plug-in customized capabilities for an application.

Let's take a look at the viewer performing VCL application on Windows.


on various platforms… Showing a PDF file created by Microsoft Word.

24 Gnostice Smart needs...Smarter solutions...


TM

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


DOCUMENT TECHNOLOGY FOR VCL AND FMX PAGE 2/3 Gnostice TM

Smart needs...Smarter solutions...

FMX application on Windows. Showing a binary DOC template file with tables.

FMX application on OS X. Showing a DOCX file.

FMX application on Android


Phone. Showing a PDF file with
non-embedded standard-14
Type1 fonts. The Document
Viewer contains lightweight
embedded font resources for
rendering non-embedded
standard fonts that must be
supported by a PDF viewer. So
these files render on an Android
device that does not have the
fonts installed.

25
DOCUMENT TECHNOLOGY FOR VCL AND FMX PAGE 3/3 Gnostice TM

Smart needs...Smarter solutions...

FMX application on Android Phone.


Showing a PDF file created by
Microsoft Word.

The viewer components provide a


broad set of APIs to enable you to
program the viewer in your
applications. Here is a list of the
main APIs of the VCL and FMX
Document Viewers’, organized by
function groups:

Function Group APIs - Properties, Methods and Events


File I/O procedure LoadFromFile(const AFileName: TFileName)
procedure LoadFromStream(AStream: TStream)
procedure CloseDocument
Page Navigation procedure FirstPage
procedure LastPage
procedure NextPage
procedure PrevPage
function GoToPage(const APageNumber: TgtPageNumber): TgtPageNumber
function GoToPage(const APageNumber: TgtPageNumber;
const ARect: TRectF): TgtPageNumber
function IsFirstPage: Boolean
function IsLastPage: Boolean
property PageNumber: TgtPageNumber
property PageCount: Integer
Event OnPageCountUpdated: TgtPageCountUpdatedEvent
Event OnPageNumberChanged: TgtPageNumberChangedEvent
Page Zoom function ZoomIn(const AStep: Integer = 1; const APageNumber: Integer = 0;
const AFocalX: Single = -1; const AFocalY: Single = -1): Single
function ZoomOut(const AStep: Integer = 1; const APageNumber: Integer = 0;
const AFocalX: Single = -1; const AFocalY: Single = -1): Single
function ZoomRect(const ARect: TRectF; const APageNumber: Integer = 0): Single
function ZoomTo(const AZoomMode: TgtxZoomMode): Single
function ZoomTo(const APercent: Single = 100; const APageNumber: Integer = 0;
const AFocalX: Single = -1; const AFocalY: Single = -1): Single
property Display.Zoom… (highlighted by a RED rectangle in image)
Page Rotation function Rotate(const RotationAngle: TgtxRotationAngle): TgtxRotationAngle
function Rotate(const RotationAngle: TgtxRotationAngle;
const PageNumber: TgtPageNumber): TgtxRotationAngle
function RotateClockwise90: TgtxRotationAngle;
function RotateCounterClockwise90: TgtxRotationAngle;
property Display.Rotation...(highlighted by an ORANGE rectangle in image)
Page Layout property Display.PagedView.PageLayout...
(highlighted by a BLUE rectangle in image)
Scroll Orientation property Display.PagedView.ScrollOrientation...
(highlighted by a GREEN rectangle in image)

This was an introduction to the new Document Viewer included in Gnostice XtremeDocumentStudio Delphi.
In the next issue we'll go deeper into specific areas of the viewer and explore more application scenarios.
XtremeDocumentStudio is a native Delphi library and does not use external libraries.
The paid Ultimate edition includes full source code.

You can download a trial of the new version of XtremeDocumentStudio at:


https://www.gnostice.com/xdoc-delphi

26 Gnostice
Smart needs...Smarter solutions...
TM

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART IV: PAGE 1 / 17
IMAGE RECOGNITION IMPROVED
BY BOIAN MITOV
starter expert trainingimages
– contains the training images
traininglabels
In the previous articles, we created projects that – contains the corresponding training labels
were able to recognize faces inside images. For the testimages
recognition, we used the gray-scale values of the – contains the test images
image pixels as features sending them to the testlabels
classifier. The pixel values are one of the simplest – contains the test labels
features that can be used for the recognition,
however they carry limited information about the Now we will create a project, using Radial Basis
overall structure of the image, and this reduces the
Function Classifier and train the classifier to
quality of the recognition. If we provide some more
meaningful features of the image. such as recognize the images.
presence and/or count of some shapes or zones, Start a new VCL Form application:
we can improve the recognition.
In this article, we will create a simple OCR
application for recognizing handwritten digits. To
train and test the classifier, we will use a training
set of characters provided by the
US Postal Service for testing and
evaluating the performance of
OCR.
The set is provided as text files.
For each set ( training, testing, etc.),
there are 2 files. One contains the
images of the digits, the other the corresponding
value (label) of each image.
The images are represented with characters for
each pixel. Each image is 28x28 pixels, and there
are 3 colors – White, Gray and Black. The white is
represented by the Space ' ' character, Gray is
represented by Plus '+', and Black by Sharp '#'.
Each image row starts with a new line.
Here is an example of how '3' is represented: Type “listview” in the Tool Palette search box,
+++######+ then select TListView from the palette:
++##########+
+############+
+###+++++####+
+++ ###+
+###+
++###
+######
++########++
+#########+
+##########+ And drop it on the form.
++++++++##+ Rename it to TrainingListView:
##+
+##+
+ +###+
++#+ +####+
###+++++####++
###########+
++#######++
+##+++
The labels file simply contains one character for
each digit, in this case '3'. Each label is on a new
line.
For this project, we will use 4 files:

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 27


ARTIFICIAL INTELLIGENCE PART IV: PAGE 2 / 17
IMAGE RECOGNITION IMPROVED
Type “image” in the Tool Palette search box , Select the TrainingImageList.
then select TImageList from the palette: Select the LargeImages property,
and click on the button next to its value.
Select the
TrainingImageList from the drop-down:

And drop it on the form.


Rename it to TrainingImageList:

to set the value of the property to


“TrainingImageList”:

In the Object Inspector set the


Repeat the same steps to add
values of the Height and Width
another TListView and
properties to 28:
TImageList.
In the Object Inspector set the
values of the Height and
Width properties of the
TImageList to “28”, and
rename it to “TestImageList” :

28 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART IV: PAGE 3 / 17
Select the second TListView. We will also add a Label to display the results of
In the Object Inspector set the value of the the recognition:
LargeImages property to “TestImageList”, and Type “label” in the Tool Palette search box , then
rename the TListView to “TestListView”: select TLabel from the palette:

And drop it on the form:


We need to add a button to start the training,
and the recognition.
Type “button” in the Tool Palette search box , then
select TButton from the palette:

You can optionally add 2 more labels on top


of the 2 TListView components and set their
captions to “Training data” and “Test data” :

And drop it on the form.


Set the Caption of the Button1 to “Run”:

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 29


ARTIFICIAL INTELLIGENCE PART IV: PAGE 4 / 17
Finally we need to add the classifier. procedure TForm1.PopulateImageList(
AImageList : TImageList; AFileName : String );
Type “radial” in the Tool Palette search box, var
then select ALines : IStringArrayList; ABitmap : Tbitmap;
TILRadialBasisFunctionNetwork I, X : Integer; APixels : PColor;
from the palette:
begin
ALines := TStringArrayList.Create();
ABitmap := TBitmap.Create();
ABitmap.Width := IMAGE_WIDTH;
ABitmap.Height := IMAGE_HEIGHT;

ALines.LoadFromFile( AFileName );

ABitmap.PixelFormat := pf32bit;
APixels := ABitmap.ScanLine[ 0 ];
And drop it on the form: Inc( APixels, IMAGE_WIDTH );

for I := 0 to ALines.Count - 1 do
begin
if((( I mod IMAGE_HEIGHT ) = 0 ) and ( I > 0 )) then
begin
AImageList.AddMasked( ABitmap, clRed );
APixels := ABitmap.ScanLine[ 0 ];
Inc( APixels, IMAGE_WIDTH );
end;

for X := 28 downto 1 do
begin
Dec( APixels );
case( ALines[ I ][ X ] ) of
'+' :
APixels^ := clGray;
Now we can start adding the code.
First, we will declare 2 constants with the '#' :
Width and Height of the images: APixels^ := clBlack;
const else
IMAGE_WIDTH = 28; APixels^ := clWhite;
IMAGE_HEIGHT = 28;
…. end;
end;
end;
Next, in the TForm1 we will add 2 private
sections, one with the fields that we will use, and AImageList.AddMasked( ABitmap, clRed );
ABitmap.Free();
one with the methods: end;

TForm1 = class(TForm)
….
private
FTrainingLabels : ISLRealBuffer;
FTestLabels : ISLRealBuffer;

private
procedure PopulateImageList( AImageList : TImageList; AFileName : String );
function LoadLabels( AFileName : String ) : ISLRealBuffer;
procedure PopulateListViewLabels( AListView : TListView; ALabels : ISLRealBuffer );
procedure PopulateListView( AListView : TListView );
….

We will use the TStringArrayList from the


Mitov.Containers.List, so we will need to add
the unit to the project:

30 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART IV: PAGE 5 / 17
Here first we create instance of TStringArrayList, function TForm1.LoadLabels( AFileName : String ) :
and TBitmap with IMAGE_WIDTH, ISLRealBuffer;
var
and IMAGE_HEIGHT size:
ALines : IStringArrayList;
ALines := TStringArrayList.Create(); I : Integer;
ABitmap := TBitmap.Create();
ABitmap.Width := IMAGE_WIDTH; begin
ABitmap.Height := IMAGE_HEIGHT; ALines := TStringArrayList.Create();
ALines.LoadFromFile( AFileName );
Load the file into the ALines: Result := TSLRealBuffer.CreateSize( ALines.Count );
ALines.LoadFromFile( AFileName );
for I := 0 to ALines.Count - 1 do
Set the bitmap format to pf32bit: Result[ I ] := ALines[ I ].ToInteger();
ABitmap.PixelFormat := pf32bit;
end;
Get a pointer to the end of the first row of pixels in the image:
APixels := ABitmap.ScanLine[ 0 ]; The code is very simple. We create and load from
Inc( APixels, IMAGE_WIDTH ); file a TStringArrayList:
ALines := TStringArrayList.Create();
Then we iterate trough all the lines: ALines.LoadFromFile( AFileName );
for I := 0 to ALines.Count - 1 do
Then create a result buffer to store the labels,
and populate it from the lines:
Check if we have finished populating the
bitmap with a full image, and if so add the Result := TSLRealBuffer.CreateSize( ALines.Count );
bitmap to the list, and get again pointer to the
for I := 0 to ALines.Count - 1 do
end of the first row of the bitmap: Result[ I ] := ALines[ I ].ToInteger();
if((( I mod IMAGE_HEIGHT ) = 0 ) and ( I > 0 )) then
begin Next we will add the code for 2 helper methods to
AImageList.AddMasked( ABitmap, clRed );
populate the TListView components with the
APixels := ABitmap.ScanLine[ 0 ];
Inc( APixels, IMAGE_WIDTH ); images, and the corresponding labels.
end; First we will add TListItem for each image, and
set the image index for the item:
Next, for each pixel in the row we assign the
corresponding color Gray, Black or White: procedure TForm1.PopulateListView( AListView :
TListView );
for X := 28 downto 1 do var I : Integer; AItem : TListItem;
begin begin
Dec( APixels ); AListView.Items.BeginUpdate();
case( ALines[ I ][ X ] ) of for I := 0 to AListView.LargeImages.Count - 1 do
'+' : APixels^ := clGray; begin
AItem := AListView.Items.Add();
'#' : APixels^ := clBlack; AItem.ImageIndex := I;
end;
else
APixels^ := clWhite; AListView.Items.EndUpdate();
end; end;
end;
The code iterates trough all the images:
And finally, after the loop ends, we add the last
for I := 0 to AListView.LargeImages.Count - 1 do
image, and free the bitmap:
AImageList.AddMasked( ABitmap, clRed ); Adds a TListItem :
ABitmap.Free();
AItem := AListView.Items.Add();
Next we will implement the method to load the
And sets its image index:
labels for each image:
AItem.ImageIndex := I;

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 31


ARTIFICIAL INTELLIGENCE PART IV: PAGE 6 / 17
Next we will implement the method to populate
the labels:
procedure TForm1.PopulateListViewLabels( AListView : TListView; ALabels : ISLRealBuffer );
var I : Integer;

begin
AListView.Items.BeginUpdate();
for I := 0 to AListView.Items.Count - 1 do
AListView.Items[ I ].Caption := Round( ALabels[ I ] ).ToString();
AListView.Items.EndUpdate();
end;

The code is very simple. We just get each label And we do the same for the testimages and
from the buffer, round it and set it as caption of testlabels files, populating the TestImageList,
the corresponding TListView item. and TestListView.
Now we need to add the methods to extract the
Now that we have the methods ready, we can use features from the images. We will declare the 2
them to read the files, and populate the methods in the class:
ImageLists and the ListViews when the form is
TForm1 = class(TForm)
created. ….
Switch back to the Form Editor by clicking on the private
“Design” tab. ….
function ExtractImageFeatures( ABmp : TBitmap ) :
Double click on the form to generate the OnCreate ISLRealBuffer;
event: function ExtractAllFeatures( AImageList :
TImageList ) : ISLRealBufferArray;
….

First, we will implement method to extract the


In the event handler, add the following code: features of one bitmap image:

procedure TForm1.FormCreate(Sender: TObject);


begin
PopulateImageList( TrainingImageList, 'C:\Data\OCR\trainingimages' );
FTrainingLabels := LoadLabels( 'C:\Data\OCR\traininglabels' );

PopulateListView( TrainingListView );
PopulateListViewLabels( TrainingListView, FTrainingLabels );

PopulateImageList( TestImageList, 'C:\Data\OCR\testimages' );


FTestLabels := LoadLabels( 'C:\Data\OCR\testlabels' );

PopulateListView( TestListView );
PopulateListViewLabels( TestListView, FTestLabels );
end;

The code is simple. We call PopulateImageList to function TForm1.ExtractImageFeatures( ABmp : TBitmap


) : ISLRealBuffer;
populate the ImageList from the trainingimages
var I : Integer; APixels : Pcolor; APtr : PReal;
file, and LoadLabels to load the labels from a file
begin
and assign them to the FTrainingLabels Result :=
ISLRealBuffer: TSLRealBuffer.CreateSize( ABmp.Width * ABmp.Height );
PopulateImageList( TrainingImageList, APtr := Result.Write();
'C:\Data\OCR\trainingimages' ); ABmp.PixelFormat := pf32bit;
FTrainingLabels := LoadLabels( APixels := ABmp.ScanLine[ ABmp.Height - 1 ];
'C:\Data\OCR\traininglabels' ); for I := 0 to ABmp.Width * ABmp.Height - 1 do
begin
if( APixels^ <> clWhite ) then APtr^ := 1
Then we will populate the corresponding TListView : else APtr^ := 0;
PopulateListView( TrainingListView ); Inc( APixels );
PopulateListViewLabels( TrainingListView, Inc( APtr );
FTrainingLabels ); end;
end;

32 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART IV: PAGE 7 / 17
In the code, we create a result buffer: And return the TSLRealBufferList as
Result := TSLRealBuffer.CreateSize( ISLRealBufferArray:
ABmp.Width * ABmp.Height ); Result := AResult;

Get a pointer to the buffer data: Next, we need to write the event handler when the
APtr := Result.Write();
button is pressed and use the ExtractAllFeatures
Set the Bitmap's PixelFormat to pf32bit, and obtain and the ExtractImageFeatures methods to train
pointer to the pixel data (Please note that TBitmap the classifier, then perform recognitions.
stores the data mirrored, so the first line is actually the To do the recognitions, we will need to add one
last line in the image): more field FResult in the TForm1, where we will
store the result from the classifier event:
ABmp.PixelFormat := pf32bit;
APixels := ABmp.ScanLine[ ABmp.Height - 1 ]; TForm1 = class(TForm)
….
private
Then we iterate through the pixels: ….
FResult : Integer;
for I := 0 to ABmp.Width * ABmp.Height - 1 do

And for each pixel set value of 0 or 1 depending


on whether it is while color:
if( APixels^ <> clWhite ) then APtr^ := 1
else APtr^ := 0;

Then increment the pointers:


Inc( APixels );
Inc( APtr ); Switch back to the Form Editor by clicking on the
“Design” tab.
Next we will implement the method to extract In the Form Editor, double-click on the Button1 to
all the features and add them to generate OnClick event handler:
ISLRealBufferArray for the training:
function TForm1.ExtractAllFeatures( AImageList : TImageList ) : ISLRealBufferArray;
var ABitmap : Tbitmap; I : Cardinal; AResult : TSLRealBufferList;
begin
AResult := TSLRealBufferList.Create();
ABitmap := TBitmap.Create();
for I := 0 to AImageList.Count - 1 do
begin
AImageList.GetBitmap( I, ABitmap );
AResult.Append( ExtractImageFeatures( ABitmap ));
end;

ABitmap.Free();

Result := AResult;
end;

Here we first create TSLRealBufferList,


and TBitmap:
AResult := TSLRealBufferList.Create();
ABitmap := TBitmap.Create();

Iterate trough all the images:


for I := 0 to AImageList.Count - 1 do

For each image extract the features, and append


them to the TSLRealBufferList:
AImageList.GetBitmap( I, ABitmap );
AResult.Append( ExtractImageFeatures( ABitmap ));
Finally free the TBitmap:
ABitmap.Free();

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 33


ARTIFICIAL INTELLIGENCE PART IV: PAGE 8 / 17
In the event handler, add the following code:
procedure TForm1.Button1Click(Sender: TObject);
var ATrainingData : ISLRealBufferArray; ATestFeatures : ISLRealBufferArray;
I : Integer; AErrorsCount : Integer;
begin
ATrainingData := ExtractAllFeatures( TrainingImageList );

for I := 0 to ATrainingData.Count - 1 do
ILRadialBasisFunctionNetwork1.Train( ATrainingData[ I ], FTrainingLabels[ I ] );

TestListView.Items.BeginUpdate();

ATestFeatures := ExtractAllFeatures( TestImageList );


AErrorsCount := 0;
for I := 0 to TestImageList.Count - 1 do
begin
ILRadialBasisFunctionNetwork1.Predict( ATestFeatures[ I ] );

if( FResult <> FTestLabels[ I ] ) then


begin
TestListView.Items[ I ].Caption := 'Error '
+ FResult.ToString() + '(' + Round( FTestLabels[ I ] ).ToString() + ')';
Inc( AErrorsCount );
end

else
TestListView.Items[ I ].Caption := FResult.ToString()
+ '(' + Round( FTestLabels[ I ] ).ToString() + ')';
end;

TestListView.Items.EndUpdate();

Label1.Caption := AErrorsCount.ToString()
+ ' Errors of ' + TestImageList.Count.ToString()
+ ' - ' + ( ( TestImageList.Count - AErrorsCount )
* 100 / TestImageList.Count ).ToString() + '% correct';
end;

In the code first we will extract all the features and assign them to ISLRealBufferArray:
ATrainingData := ExtractAllFeatures( TrainingImageList );

Iterate trough all the buffers in the array, and train the classifier:
for I := 0 to ATrainingData.Count - 1 do
ILRadialBasisFunctionNetwork1.Train( ATrainingData[ I ], FTrainingLabels[ I ] );
Once the training is done, we can perform recognitions.
Again, extract features from all the test images:
ATestFeatures := ExtractAllFeatures( TestImageList );
Zero the error counter: AErrorsCount := 0;
Iterate through all the elements and perform the recognition:
for I := 0 to TestImageList.Count - 1 do
begin
ILRadialBasisFunctionNetwork1.Predict( ATestFeatures[ I ] );
Then check the result and update the TListItem captions with it:
if( FResult <> FTestLabels[ I ] ) then
begin
TestListView.Items[ I ].Caption :=
'Error ' + FResult.ToString() + '(' + Round( FTestLabels[ I ] ).ToString() + ')';
Inc( AErrorsCount );
end
else
TestListView.Items[ I ].Caption := FResult.ToString() + '(' + Round( FTestLabels[ I ] ).ToString() +
')';

34 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART IV: PAGE 9 / 17
Finally we display the recognition statistics
in the Label1:
Label1.Caption := AErrorsCount.ToString() + ' Errors of ' + TestImageList.Count.ToString() + ' -
' + ( ( TestImageList.Count - AErrorsCount ) * 100 / TestImageList.Count ).ToString() + '% correct';

The only thing that remains to be done is getting The code is very simple. If nothing has been
the result from the recognition from the classifier. recognized, then set the FResult to -1:
Switch to the Form Editor by clicking on the “Design” if( AResult.Count = 0 ) then
tab. begin
Select the ILRadialBasisFunctionNetwork1 FResult := -1;
Exit;
component. In the object inspector click on the
end;
“Events” tab. Double click on the value of the
OnResult event to generate event handler:
Otherwise round and set the recognized category
(Label) into FResult:
In the event handler, FResult := Round( AResult[ 0 ].Neuron.Category );
add the following code:
The application is ready. If we run it, and click on
the "Run" button, we will see the result:

procedure
TForm1.ILRadialBasisFunctionNetwork1Result(ASe
nder: TObject;
AFeatures: ISLRealBuffer; AResult:
TILRadialBasisFunctionResult);
begin
if( AResult.Count = 0 ) then
begin As you can see we have 109 errors over 1000
FResult := -1; images or 89.1 % correct recognition, which is
Exit; not bad for such hand scribbled digits.
end;

FResult := Round( AResult[ 0 ].Neuron.Category );


end;
ARTIFICIAL INTELLIGENCE PART IV: PAGE 10 / 17
Here is the complete code of the application:
unit Unit1;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.ImageList, Vcl.ImgList,
Vcl.ComCtrls, Mitov.Types, LPComponent, ILBasicClassifier,
ILRadialBasisFunctionNetwork, Vcl.StdCtrls;

type
TForm1 = class(TForm)
TrainingListView: TListView;
TrainingImageList: TImageList;
TestListView: TListView;
TestImageList: TImageList;
Button1: TButton;
Label1: TLabel;
Label2: TLabel;
Label4: TLabel;
ILRadialBasisFunctionNetwork1: TILRadialBasisFunctionNetwork;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure ILRadialBasisFunctionNetwork1Result(ASender: TObject;
AFeatures: ISLRealBuffer; AResult: TILRadialBasisFunctionResult);
private
FTrainingLabels : ISLRealBuffer;
FTestLabels : ISLRealBuffer;
FResult : Integer;

private
procedure PopulateImageList( AImageList : TImageList; AFileName : String );
function LoadLabels( AFileName : String ) : ISLRealBuffer;
procedure PopulateListView( AListView : TListView );
procedure PopulateListViewLabels( AListView : TListView; ALabels : ISLRealBuffer );
function ExtractImageFeatures( ABmp : TBitmap ) : ISLRealBuffer;
function ExtractAllFeatures( AImageList : TImageList ) : ISLRealBufferArray;

public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

uses
Mitov.Containers.List;

const
IMAGE_WIDTH = 28;
IMAGE_HEIGHT = 28;

36 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART IV: PAGE 11 / 17
procedure TForm1.PopulateImageList( procedure TForm1.PopulateListView(
AImageList : TImageList; AFileName : String ); AListView : TListView );
var ALines : IStringArrayList; ABitmap : TBitmap; var I : Integer; AItem : TListItem;
I : Integer; X : Integer; APixels : PColor; begin
begin AListView.Items.BeginUpdate();
ALines := TStringArrayList.Create(); for I := 0 to AListView.LargeImages.Count - 1 do
ABitmap := TBitmap.Create(); begin
ABitmap.Width := IMAGE_WIDTH; AItem := AListView.Items.Add();
ABitmap.Height := IMAGE_HEIGHT; AItem.ImageIndex := I;
end;
ALines.LoadFromFile( AFileName ); AListView.Items.EndUpdate();
end;
ABitmap.PixelFormat := pf32bit;
APixels := ABitmap.ScanLine[ 0 ];
Inc( APixels, IMAGE_WIDTH ); procedure TForm1.PopulateListViewLabels(
AListView : TListView; ALabels : ISLRealBuffer );
for I := 0 to ALines.Count - 1 do var I : Integer;
begin begin
if((( I mod IMAGE_HEIGHT ) = 0 ) and ( I > 0 )) then AListView.Items.BeginUpdate();
begin for I := 0 to AListView.Items.Count - 1 do
AImageList.AddMasked( ABitmap, clRed ); AListView.Items[ I ].Caption := Round( ALabels[ I ]
APixels := ABitmap.ScanLine[ 0 ]; ).ToString();
Inc( APixels, IMAGE_WIDTH );
end; AListView.Items.EndUpdate();
end;
for X := 28 downto 1 do
begin procedure TForm1.FormCreate(Sender: TObject);
Dec( APixels ); begin
case( ALines[ I ][ X ] ) of PopulateImageList( TrainingImageList,
'+' : 'C:\Data\OCR\trainingimages' );
APixels^ := clGray; FTrainingLabels := LoadLabels(
'C:\Data\OCR\traininglabels' );
'#' : PopulateListView( TrainingListView );
APixels^ := clBlack; PopulateListViewLabels(
TrainingListView, FTrainingLabels );
else
PopulateImageList( TestImageList,
APixels^ := clWhite;
'C:\Data\OCR\testimages' );
end; FTestLabels :=
end; LoadLabels( 'C:\Data\OCR\testlabels' );
end; PopulateListView( TestListView );
PopulateListViewLabels( TestListView,
AImageList.AddMasked( ABitmap, clRed ); FTestLabels );
ABitmap.Free(); end;
end;

function TForm1.LoadLabels( AFileName : String ) : ISLRealBuffer;


var ALines : IStringArrayList; I : Integer;
begin
ALines := TStringArrayList.Create();
ALines.LoadFromFile( AFileName );

Result := TSLRealBuffer.CreateSize( ALines.Count );

for I := 0 to ALines.Count - 1 do
Result[ I ] := ALines[ I ].ToInteger();
end;

procedure TForm1.PopulateListView( AListView : TListView );


var I : Integer; AItem : TListItem;
begin
AListView.Items.BeginUpdate();
for I := 0 to AListView.LargeImages.Count - 1 do
begin
AItem := AListView.Items.Add();
AItem.ImageIndex := I;
end;

AListView.Items.EndUpdate();
end;

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 37


ARTIFICIAL INTELLIGENCE PART IV: PAGE 12 / 17
function TForm1.ExtractImageFeatures( ABmp : TBitmap ) : ISLRealBuffer; var I : Integer;
APixels : Pcolor; APtr : PReal;
begin
Result := TSLRealBuffer.CreateSize( ABmp.Width * ABmp.Height );
APtr := Result.Write();
ABmp.PixelFormat := pf32bit;
APixels := ABmp.ScanLine[ ABmp.Height - 1 ];
for I := 0 to ABmp.Width * ABmp.Height - 1 do
begin
if( APixels^ <> clWhite ) then APtr^ := 1
else APtr^ := 0;
Inc( APixels );
Inc( APtr );
end;
end;

function TForm1.ExtractAllFeatures( AImageList : TImageList ) : ISLRealBufferArray;


var ABitmap : Tbitmap; I : Cardinal; Aresult : TSLRealBufferList;
begin
AResult := TSLRealBufferList.Create();
ABitmap := TBitmap.Create();
for I := 0 to AImageList.Count - 1 do
begin
AImageList.GetBitmap( I, ABitmap );
AResult.Append( ExtractImageFeatures( ABitmap ));
end;
ABitmap.Free();
Result := AResult;
end;

procedure TForm1.Button1Click(Sender: TObject);


var ATrainingData : ISLRealBufferArray; ATestFeatures : ISLRealBufferArray;
I : Integer; AErrorsCount : Integer;
begin
ATrainingData := ExtractAllFeatures( TrainingImageList );

for I := 0 to ATrainingData.Count - 1 do
ILRadialBasisFunctionNetwork1.Train( ATrainingData[ I ], FTrainingLabels[ I ] );

TestListView.Items.BeginUpdate();

ATestFeatures := ExtractAllFeatures( TestImageList );


AErrorsCount := 0;
for I := 0 to TestImageList.Count - 1 do
begin
ILRadialBasisFunctionNetwork1.Predict( ATestFeatures[ I ] );

if( FResult <> FTestLabels[ I ] ) then


begin
TestListView.Items[ I ].Caption := 'Error '
+ FResult.ToString()
+ '(' + Round( FTestLabels[ I ] ).ToString() + ')';
Inc( AErrorsCount );
end
else
TestListView.Items[ I ].Caption := FResult.ToString()
+ '(' + Round( FTestLabels[ I ] ).ToString() + ')';
end;

TestListView.Items.EndUpdate();

Label1.Caption := AErrorsCount.ToString()
+ ' Errors of ' + TestImageList.Count.ToString() + ' - '
+ ( ( TestImageList.Count - AErrorsCount ) * 100 / TestImageList.Count ).ToString() + '% correct';
end;

38 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART IV: PAGE 13 / 17
procedure TForm1.ILRadialBasisFunctionNetwork1Result(ASender: TObject;
AFeatures: ISLRealBuffer; AResult: TILRadialBasisFunctionResult);
begin
if( AResult.Count = 0 ) then
begin
FResult := -1;
Exit;
end;

FResult := Round( AResult[ 0 ].Neuron.Category );


end;

end.

Although the recognition is quite good, there are


ways to improve it further. One of the factors
affecting the quality of the recognition is the feature
extractor, and the features that are sent to the
classifier. In the application, we simply used the
Black/White value of each pixel as feature. This
provides relatively limited information about the
overall image and its characteristics. If we look at
the digits, one of the things to notice is that some
digits such as 6, 8, 9, and 0 have closed white areas
surrounded by black. 0, 6, and 9 as example have 1
such circular area, where 8 has 2. If we can extract
this information from the image and send it to the And drop it on the form.
classifier, it should help to improve the recognition. Double click on the component to generate the
VisionLab has a component called OnComponents event handler:
TVLConnectedComponents that can extract the
number of such areas (called connected components)
from the image. We will use it to improve the OCR.

We will add one more field in the TForm1 to store


the count of white zones (also called connected
components):

TForm1 = class(TForm)
private

FCountZones : Integer;

We also will need to add 2 more units to the uses:


uses
Mitov.Containers.List,
System.Math, VCL.Mitov.ImageBuffer;

Switch to the Form Editor by clicking on the “Design”


tab.
Type “connected” in the Tool Palette search box, then
select TVLConnectedComponents from the palette:

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 39


ARTIFICIAL INTELLIGENCE PART IV: PAGE 14 / 17
In the event handler add the following code All we needed to do was to add “+ 1” to the size
to store the number of zones in FCountZones: of the buffer in CreateSize, then call:
procedure VLConnectedComponents1.ProcessFrame(
TForm1.VLConnectedComponents1Components( TVLImageBuffer.CreateBmp( ABmp ) );
Sender: TObject; Count: Cardinal);
begin
FCountZones := System.Math.Max( 0, Count - 1 ); This creates buffer out of the bitmap, and
end; sends it to VLConnectedComponents1 for
processing.
Now we can add the FCountZones as feature Finally set the “FCountZones / 5” as last
to the ExtractImageFeatures: feature:
function TForm1.ExtractImageFeatures( ABmp : TBitmap ) :
ISLRealBuffer; Result[ IMAGE_WIDTH * IMAGE_HEIGHT ] := FCountZones / 5;
var I : Integer; APixels : Pcolor; APtr : PReal;
begin
Result := TSLRealBuffer.CreateSize( We divide it by 5 to get it smaller than 1
IMAGE_WIDTH * IMAGE_HEIGHT + 1 ); within similar range to the other features.
APtr := Result.Write();
ABmp.PixelFormat := pf32bit;
APixels := ABmp.ScanLine[ ABmp.Height - 1 ];
for I := 0 to ABmp.Width * ABmp.Height - 1 do
begin
if( APixels^ <> clWhite ) then APtr^ := 1
else APtr^ := 0;

Inc( APixels );
Inc( APtr );
end;
VLConnectedComponents1.ProcessFrame(
The application is ready. If we run it, and
TVLImageBuffer.CreateBmp( ABmp ) );
click on the “Run” button, we will see the
Result[ IMAGE_WIDTH * IMAGE_HEIGHT ] := FCountZones / 5; result:
end;

40 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART IV: PAGE 15 / 17
You can see that now we have 108 errors instead
of 109, which is an improvement. If we continue
to add more relevant features, this will further
improve the recognition.
Here is the complete code of the improved
application:
unit Unit1;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.ImageList, Vcl.ImgList,
Vcl.ComCtrls, Mitov.Types, LPComponent, ILBasicClassifier,
ILRadialBasisFunctionNetwork, Vcl.StdCtrls, SLCommonFilter, VLCommonFilter,
VLConnectedComponents;

type
TForm1 = class(TForm)
TrainingListView: TListView;
TrainingImageList: TImageList;
TestListView: TListView;
TestImageList: TImageList;
Button1: TButton;
Label1: TLabel;
Label2: TLabel;
Label4: TLabel;
ILRadialBasisFunctionNetwork1: TILRadialBasisFunctionNetwork;
VLConnectedComponents1: TVLConnectedComponents;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure ILRadialBasisFunctionNetwork1Result(ASender: TObject;
AFeatures: ISLRealBuffer; AResult: TILRadialBasisFunctionResult);
procedure VLConnectedComponents1Components(Sender: TObject;
Count: Cardinal);
private
FTrainingLabels : ISLRealBuffer;
FTestLabels : ISLRealBuffer;
FResult : Integer;
FCountZones : Integer;

private
procedure PopulateImageList( AImageList : TImageList; AFileName : String );
function LoadLabels( AFileName : String ) : ISLRealBuffer;
procedure PopulateListView( AListView : TListView );
procedure PopulateListViewLabels( AListView : TListView; ALabels : ISLRealBuffer );
function ExtractImageFeatures( ABmp : TBitmap ) : ISLRealBuffer;
function ExtractAllFeatures( AImageList : TImageList ) : ISLRealBufferArray;

public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

uses
Mitov.Containers.List, System.Math, VCL.Mitov.ImageBuffer;

const
IMAGE_WIDTH = 28;
IMAGE_HEIGHT = 28;

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 41


ARTIFICIAL INTELLIGENCE PART IV: PAGE 16 / 17
procedure TForm1.PopulateImageList( procedure TForm1.PopulateListViewLabels(
AImageList : TImageList; AFileName : String ); AListView : TListView; ALabels : ISLRealBuffer );
var ALines : IStringArrayList; ABitmap : TBitmap; var I : Integer;
I : Integer; X : Integer; APixels : PColor; begin
begin AListView.Items.BeginUpdate();
ALines := TStringArrayList.Create(); for I := 0 to AListView.Items.Count - 1 do
ABitmap := TBitmap.Create(); AListView.Items[ I ].Caption := Round( ALabels[ I ] )
ABitmap.Width := IMAGE_WIDTH; .ToString();
ABitmap.Height := IMAGE_HEIGHT; AListView.Items.EndUpdate();
end;
ALines.LoadFromFile( AFileName );
procedure TForm1.FormCreate(Sender: TObject);
ABitmap.PixelFormat := pf32bit; begin
APixels := ABitmap.ScanLine[ 0 ]; PopulateImageList( TrainingImageList,
Inc( APixels, IMAGE_WIDTH );
'C:\Data\OCR\trainingimages' );
for I := 0 to ALines.Count - 1 do FTrainingLabels := LoadLabels(
begin 'C:\Data\OCR\traininglabels' );
if((( I mod IMAGE_HEIGHT ) = 0 ) and ( I > 0 )) then PopulateListView( TrainingListView );
begin PopulateListViewLabels( TrainingListView,
AImageList.AddMasked( ABitmap, clRed ); FTrainingLabels );
APixels := ABitmap.ScanLine[ 0 ];
Inc( APixels, IMAGE_WIDTH ); PopulateImageList( TestImageList,
end; 'C:\Data\OCR\testimages' );
FTestLabels := LoadLabels(
for X := 28 downto 1 do ‚C:\Data\OCR\testlabels' );
begin
Dec( APixels ); PopulateListView( TestListView );
case( ALines[ I ][ X ] ) of PopulateListViewLabels( TestListView,
'+' : APixels^ := clGray; FTestLabels );
'#' : APixels^ := clBlack; end;

else function TForm1.ExtractImageFeatures(


APixels^ := clWhite; ABmp : TBitmap ) : ISLRealBuffer;
var I : Integer; APixels : Pcolor; APtr : PReal;
end; begin
end; Result := TSLRealBuffer.CreateSize(
end; IMAGE_WIDTH * IMAGE_HEIGHT + 1 );
APtr := Result.Write();
AImageList.AddMasked( ABitmap, clRed ); ABmp.PixelFormat := pf32bit;
ABitmap.Free(); APixels := ABmp.ScanLine[ ABmp.Height - 1 ];
end; for I := 0 to ABmp.Width * ABmp.Height - 1 do
begin
function TForm1.LoadLabels( AFileName : String ) : if( APixels^ <> clWhite ) then APtr^ := 1
ISLRealBuffer; else APtr^ := 0;
var ALines : IStringArrayList; I : Integer;
begin Inc( APixels );
ALines := TStringArrayList.Create(); Inc( APtr );
ALines.LoadFromFile( AFileName ); end;

Result := TSLRealBuffer.CreateSize( ALines.Count ); VLConnectedComponents1.ProcessFrame(


TVLImageBuffer.CreateBmp( ABmp ) );
for I := 0 to ALines.Count - 1 do Result[ IMAGE_WIDTH * IMAGE_HEIGHT ] :=
Result[ I ] := ALines[ I ].ToInteger(); FCountZones / 5;
end; end;

procedure TForm1.PopulateListView( AListView : function TForm1.ExtractAllFeatures(


TListView ); AImageList : TImageList ) : ISLRealBufferArray;
var I : Integer; AItem : TListItem; var ABitmap : Tbitmap; I : Cardinal;
begin AResult : TSLRealBufferList;
AListView.Items.BeginUpdate(); begin
for I := 0 to AListView.LargeImages.Count - 1 do AResult := TSLRealBufferList.Create();
begin ABitmap := TBitmap.Create();
AItem := AListView.Items.Add(); for I := 0 to AImageList.Count - 1 do
AItem.ImageIndex := I; begin
end; AImageList.GetBitmap( I, ABitmap );
AResult.Append( ExtractImageFeatures( ABitmap ));
AListView.Items.EndUpdate(); end;
end; ABitmap.Free();
Result := AResult;
end; 42

42 Issue Nr 1 2017 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART IV: PAGE 17 / 17
procedure TForm1.Button1Click(Sender: TObject);
var ATrainingData : ISLRealBufferArray; ATestFeatures : ISLRealBufferArray;
I : Integer; AErrorsCount : Integer;
begin
ATrainingData := ExtractAllFeatures( TrainingImageList );

for I := 0 to ATrainingData.Count - 1 do
ILRadialBasisFunctionNetwork1.Train( ATrainingData[ I ], FTrainingLabels[ I ] );

TestListView.Items.BeginUpdate();

ATestFeatures := ExtractAllFeatures( TestImageList );


AErrorsCount := 0;
for I := 0 to TestImageList.Count - 1 do
begin
ILRadialBasisFunctionNetwork1.Predict( ATestFeatures[ I ] );

if( FResult <> FTestLabels[ I ] )


then
begin
TestListView.Items[ I ].Caption := 'Error ' + FResult.ToString()
+ '(' + Round( FTestLabels[ I ] ).ToString() + ')';
Inc( AErrorsCount );
end

else
TestListView.Items[ I ].Caption := FResult.ToString()
+ '(' + Round( FTestLabels[ I ] ).ToString() + ')';
end;

TestListView.Items.EndUpdate();

Label1.Caption := AErrorsCount.ToString() + ' Errors of '


+ TestImageList.Count.ToString()
+ ' - ' + ( ( TestImageList.Count - AErrorsCount )
* 100 / TestImageList.Count ).ToString() + '% correct';
end;

procedure TForm1.ILRadialBasisFunctionNetwork1Result(ASender: TObject;


AFeatures: ISLRealBuffer; AResult: TILRadialBasisFunctionResult);
begin
if( AResult.Count = 0 ) then
begin
FResult := -1;
Exit;
end;

FResult := Round( AResult[ 0 ].Neuron.Category );


end;

procedure TForm1.VLConnectedComponents1Components(Sender: TObject; Count: Cardinal);


begin
FCountZones := System.Math.Max( 0, Count - 1 );
end;

end.

In this article, I showed you that selecting what features are sent to the classifier affects the
quality of the recognition. Sending more relevant features results in better recognition.
The classifier training and recognition is a slow and CPU consuming process.
In the following articles, we will look into ways to improve the training and recognition
performance by utilizing parallel execution over multiple cores, or if a programmable OpenCL
enable GP GPU is available on the system, by executing the code in the GPU cores.

Issue Nr 1 2017 BLAISE PASCAL MAGAZINE 43


Delphi Berlin 1.2 / View on the Museum Eiland

V. 5.00 SHORTLY TO BE RELEASED WITH:


- New!
Native BSON, JSON, MessagePack, YAML and XML. - Native improved XSD importer
Convert forth and back via unified object for generating marshal
notation object trees! able Delphi objects from XML schemas.
- New! - High speed, unified database access
Marshal Delphi objects to and from BSON, JSON, (35+ supported database APIs) with connection
MessagePack, YAML and XML. pooling, metadata and data caching on all tiers
- New! - Multi head access to the application server,
High performance and simple Spring Boot inspired REST via REST/AJAX, native binary, Publish/Subscribe, SOAP,
with full Delphi object support. XML, RTMP from web browsers, embedded devices,
- New! linked application servers, PCs, mobile devices, Java
High performance HTTP.SYS server transport for REST. systems and many more clients
- New! - Full FastCGI hosting support.
High quality pronouncable password Host PHP/Ruby/Perl/Python applications in kbmMW!
generator framework! - Native AMQP support ( Advanced Message Queuing
- New! Protocol) with AMQP 0.91 client side gateway
High quality random functions for cryptographic use! support and sample.
- New! Many improvements - Fully end 2 end secure brandable Remote Desktop
– Improved log framework with backup rollover and with near REALTIME HD video, 8 monitor support,
LogFormatter, REST CORS support, custom enum texture detection, compression and clipboard sharing.
support for object marshalling, improved scheduler - Bundled kbmMemTable Professional
features, anonymous function callback in WIB, which is the fastest and most feature rich in
improved data resolver features and more. memory table for Embarcadero products.

- Multimonitor remote desktop V5 (VCL and FMX) kbmMemTable is the fastest and most feature rich in
- Rad studio and Delphi 2009 to memory table for Embarcadero products.
Latest 10.1 BERLIN support - Easily supports large datasets with millions of records
- Win32, Win64, Android, IOS 32, IOS 64 - Easy data streaming support
and OSX client and server support! - Optional to use native SQL engine
- Native PHP, Java, OCX, ANSI C, C#, - Supports nested transactions and undo
Apache Flex client support! - Native and fast build in M/D, aggregation /grouping,
- High performance LZ4 and Jpeg compression range selection features
- Native high performance 100% developer defined app - Advanced indexing features for extreme performance
server with support for loadbalancing and failover

COMPONENTS
DEVELOPERS 4 DX
EESB, SOA,MoM, EAI TOOLS FOR INTELLIGENT SOLUTIONS. kbmMW IS THE PREMIERE N-TIER PRODUCT FOR DELPHI /
C++BUILDER BDS DEVELOPMENT FRAMEWORK FOR WIN 32 / 64, .NET AND LINUX WITH CLIENTS RESIDING ON WIN32 / 64, .NET, LINUX, UNIX
MAINFRAMES, MINIS, EMBEDDED DEVICES, SMART PHONES AND TABLETS.

Vous aimerez peut-être aussi