Well the other day I sat down and decided to sort this out, because in Smart Mobile Studio we have managed to re-create, implement and pretty much provide a full infrastructure for cutting edge application development — so why not bytes, steams, buffers and memory allocations? Well keep on reading and I’ll explain why. In detail.
But first; ByteRage has been successfully ported to Smart Mobile Studio ! If you want to know what all the fuzz is about and what ByteRage can do for you – then you can download the Delphi version here: https://code.google.com/p/byterage/source/browse/trunk/brage.pas
Byterage for Smart Pascal
As a result of this endeavour the Smart Mobile Studio RTL has gained a few units these past weeks, most notably System.Interop and SmartCL.Legacy. Which means you don’t have to download byterage for Smart Pascal, It’s now a part of Smart Mobile Studio and will ship with the next update.
Let’s have a peek at what System.Interop and SmartCL.legacy contains. I’ll also talk a bit about a few other important features I am adding to the next update (but rest assured, there is much much more in the update than this!).
What is unique about byterage buffers is:
- It can scale without losing it’s content
- Even large buffers can be serialized safely
- Has a large number of export methods
- Provides bit manipulation for entire allocated buffer
- Can be stored and loaded as JSON
- .. and much more
Exporting data from a buffer is imperative. As of writing the following export methods are available:
- Array of Byte
- Buffer clone
- Buffer segment
- UInt8Array clone
- UInt8Array segment
- Raw string
This should more than cover the export needs of getting data out of the buffer on byte-level. And naturally all export methods have a corresponding import function. So getting data back into a buffer is equally simple.
If you are curious about buffers and views you can have a peek at w3buffers.pas in the Smart RTL right now, it’s been a part of Smart Mobile Studio since the beginning. There you will find wrapper classes for the buffer-view mechanisms I have described above.
Then there is a novelty, namely bit manipulation. Now being able to set or get a bit inside a single integer or byte is handy, but it’s rare that it serves any real purpose. It’s only when you can treat a whole buffer as a collection of bits that things get interesting. Typically such “bit-maps” are used to keep track of open slots or re-cycle the use of a fixed set of elements. It can be a sprite-engine for a game which only can display 32 elements on-screen at once, or a database which needs to keep track of what pages are available inside a data-file. Either way, the buffer class provides full bit access on a global (buffer) scale.
As you have seen the fundamental buffer class is extremely versatile and provides pretty much everything you can ask for. You can read and write into a buffer at any offset, export it in a myriad of ways and even scale it. The only thing missing from the original Delphi byterage library, is the ability to insert data at any point in the buffer (as opposed to over-writing data). This is very handy for database work where you want to shrink (compact, compress) the database-file on occasion. Microsoft Access is a typical example, where calling “compress” removes empty pages from the file and re-organize records so pages are stored in-sequence inside the file.
So buffers are pretty cool!
On top of the buffer architecture I have implemented streams. Good old Delphi like streams, with TStreamReader and TStreamWriter classes to boot – allowing you to write into an advancing buffer (which is essentially the only difference between a buffer and a stream) just like you would do under FreePascal or Delphi. And the bonus is that the content of the streams on binary level, are identical to those produced with a “real” language. This means (as implied by the name “inter-operation”) that we can now share files between Delphi and Smart. If you have a custom file-format you want to bundle with your HTML5 applications you no longer need to pre-convert them to JSON. This is especially good news for RPC communication, like RemObjects SDK binary protocol which can finally be supported. Although I need to get LZH compression in here first.
Streams in combination with buffers opens up for a whole wealth of interesting communication between components as well. TW3Image can now load images directly from a stream. TW3Canvas now has a ToStream() function, allowing for 1:1 population of an on-screen image.
Streams makes us less dependent on the rules of the browser. For instance, implementing a PNG or JPG reader can now be done more or less exactly as it would be done in Delphi or C#. Although I would not recommend it for speed reasons – it’s now safely within the bounds of what can be achieved.
Perhaps you have a huge 3d mesh file you want to use with OpenGL? Stored in binary format you say? 64bit floating point? Well that’s no longer an issue. Just load the data into a stream and start reading the values, just like you would any other native language.
One of the mistakes I did when writing the original Smart Pascal RTL, was to sculpt TW3Canvas according to HTML5. I should in fact have re-created good old TCanvas from Delphi, juiced it up with all the cool new functions HTML5 provides – and made that the de-facto standard canvas.
I hardly think there has been a question so frequently asked as “how to I draw graphics in Smart Mobile Studio”. People coming straight from Delphi or FreePascal/Lazarus are so adjusted to TCanvas that the new HTML5 canvas comes across as alien. So in a perfect world I could go back in time 4 years and do that part over again, but sadly that is not possible.
The legacy unit intends to change all that. It provides a re-implementation of good old TCanvas. So if you find yourself porting over Delphi code or just want to play around with HTML5 and see what Smart Pascal can do, then you will enjoy this unit wholeheartedly. Whenever you need to draw graphics you can now use TBitmap and TCanvas just like under Delphi.
So you get these old gems out of the box:
Just to underline: one of the cool things about the HTML5 canvas, is that you can use any image as your pencil. This is also possible under Win32 using Delphi’s TCanvas, but you have to create a custom brush, select that into the device context, then put back the old system brush after your operation (as well as many more steps). So understandably very few programmers bother doing this and opt for a third-party graphics library instead (like Graphics32 or ImageEN).
SmartCL.Legacy will no doubt grow, especially now that we have streams under wrap. I will try to port over as much of classical object pascal as I can, one class at a time.
You may not believe this but HTML5, neither the canvas nor the DOM, comes with proper font measurement! You have one rudimentary function, but having to create a graphics context and a canvas object just to measure the size of a text is ridicules. So I decided to do something about that as well.
For those of you that follow my blog I’m sure you remember the “Fonts once and for all” post a while back? Well in short I created the means of measuring fonts, which was one hell of a challenge. But now that the QTX library is being merged with the Smart RTL you will be able to enjoy that as well.
SmartCL.Fonts have been given a full overhaul and now includes a class named TW3FontDetector which takes care of business.
Note: You don’t create an instance of this class to measure text. The class indexes the browser’s fonts and families which takes a few milliseconds, so you don’t want to create an instance every time you need this. Instead, you simply access the unit-function “W3FontDetector” which returns an instance which is automatically created when your application starts.
So for measuring a piece of text you would write something like:
var mSize:TW3TextMetric; mSize:=W3FontDetector.MeasureText("verdana",12,"this is my text here");
Now here comes the cool part: Access to font measurement has now been built into TW3CustomControl! When you use the methods in TW3CustomControl (which means the font measurement stuff is available almost anywhere) you don’t have to populate the font-name and pixel-size, the component will find out what font is selected into the element and it’s size automatically.
So finally calculating content size, element height and so on for your custom-controls is a walk in the park.
Here is a peek-preview of the interop unit’s interface:
And what would the world be without a preview of the legacy unit:
(* This unit provides legacy support for Delphi/FreePascal TCanvas class. Although highly extended. Notable-Changes: -- Both Brush and Pen support bitmap patters for drawing -- Stroke must be called to realize graphics operations *) TBrushStyle = ( bsSolid, bsClear ); TPenStyle = ( psSolid, psClear ); TCanvas = Class; TCanvasPatternRepeatMode = (prRepeat,prRepeatX,prRepeatY,prNoRepeat); TPaintObject = Class(TW3OwnedObject) private FBitmap: TW3Image; FPattern: JCanvasPattern; FRepeat: TCanvasPatternRepeatMode; protected function getFillStyle:THandle;virtual; procedure setRepeat(Value:TCanvasPatternRepeatMode);virtual; public Property BitmapPattern:TCanvasPatternRepeatMode read FRepeat write setRepeat; Property Bitmap:TW3Image read FBitmap; property Color:TColor; Procedure Clear;virtual; Constructor Create(AOwner:TCanvas);reintroduce;virtual; Destructor Destroy;Override; end; TBrush = Class(TPaintObject) public Property Style:TBrushStyle; Constructor Create(AOwner:TCanvas);override; end; TPen = Class(TPaintObject) public Property Mode:TPenStyle; Property Width:Integer; Constructor Create(AOwner:TCanvas);override; end; TCanvas = Class(TObject) private FContext: TW3CustomGraphicContext; FBrush: TBrush; FPen: TPen; FDC: JCanvasRenderingContext2D; FTempPxl: TW3ImageData; FPos: TPoint; FBounds: TRect; FWidth: Integer; FHeight: Integer; protected function getPixelDataBuffer:TW3ImageData; function getValidPoint(const dx,dy:Integer):Boolean; function getPixel(const x,y:Integer):TColor; procedure setPixel(const x,y:Integer;const value:TColor); function getPixelData(const x,y:Integer):TW3RGBA; procedure setPixelData(const x,y:Integer;const value:TW3RGBA); function getR(const x,y:Integer):Byte; function getG(const x,y:Integer):Byte; function getB(const x,y:Integer):Byte; function getA(const x,y:Integer):Byte; public Property Width:Integer read FWidth; property Height:Integer read FHeight; Property BoundsRect:TRect read FBounds; Property Context:TW3CustomGraphicContext read FContext; Property Brush:TBrush read FBrush; property Pen:TPen read FPen; Property Pixels[const x,y:Integer]:TColor read getPixel write setPixel; Property R[const x,y:Integer]:Byte read getR; Property G[const x,y:Integer]:Byte read getG; property B[const x,y:Integer]:Byte read getB; property A[const x,y:Integer]:Byte read getA; Property PixelData[const x,y:Integer]:TW3RGBA read getPixelData write setPixelData; Procedure MoveTo(const dx,dy:Integer); procedure LineTo(const dx,dy:Integer); procedure Line(const x1,y1,x2,y2:Integer);overload; procedure Line(const x1,y1,x2,y2:Float);overload; Procedure Hline(const x1,y1,width:Integer); Procedure VLine(const x1,y1,Height:Integer); Procedure Rectangle(const aRect:TRect);overload; procedure Rectangle(const x1,y1,x2,y2:Integer);overload; Procedure FillRect(const aRect:TRect);overload; procedure FillRect(const x1,y1,x2,y2:Integer);overload; function ToDataURL(aMimeType: String): String; function ToImageData: TW3ImageData; function ToBytes: JUint8Array; function ToBuffer:TW3MemoryBuffer; function ToStream:TStream; Procedure Stroke; Constructor Create(const aContext:TW3CustomGraphicContext); Destructor Destroy;Override; end; TPixelFormat =(pf32Bit); TBitmap = Class(TObject) private FCanvas: TCanvas; FContext: TW3GraphicContext; FWidth: Integer; FHeight: Integer; FOnLost: TNotifyEvent; FOnGained: TNotifyEvent; protected function getWidth:Integer; procedure setWidth(Value:Integer); function getHeight:Integer; procedure setHeight(Value:Integer); Procedure ReCreateBitmap; function getDC:JCanvasRenderingContext2D; public Property Empty:Boolean read (FCanvas<>NIL); Property DC:JCanvasRenderingContext2D read getDC; Property Canvas:TCanvas read FCanvas; Procedure Allocate(aWidth,aHeight:Integer); Procedure Release; Constructor Create;virtual; Destructor Destroy;Override; published Property PixelFormat:TPixelFormat; Property Width:Integer read getWidth write setWidth; property Height:Integer read getHeight write setHeight; Property OnBitmapAllocated:TNotifyEvent read FOnGained write FOnGained; Property OnBitmapReleased:TNotifyEvent read FOnLost write FOnLost; end;