Archive

Posts Tagged ‘Canvas’

Buffered Image for SmartMS

May 24th, 2012

Here is a small class to facilitate working with off-screen dynamic images in Smart MS: w3BufferedImage.zip (1kb)
It’ll be in the next Smart update, but you can already use it, it was introduced as part of WarTrail, as a way to optimize graphic elements that are complex and don’t change over several frames (text, tiled background, etc.).

You can use it to bundle graphic layer element, for instance in WarTrail the top & bottom areas (with scores & buttons) are in two distinct buffered images, and when you bring up the “menu” for a tower upgrade, that’s another buffered image.

Setup

To use it, you simply create it, and specify its size, f.i. a 200×50 buffer is created with

buffer := TBufferedImage.Create(200, 50);

then you need to setup its OnRedraw event,  which is a procedure that passes the buffered image itself. This is here that you’ll have to (re)draw whatever your buffered image is meant to contain

buffer.OnRedraw :=
   procedure (Sender : TBufferedImage)
   begin
      Sender.Canvas... your code here ...
   end;

Inplace of the anonymous methods, you can also use a standalone procedure or a regular method (and it might be preferable if what you draw isn’t trivial), as they’re all compatible, the compiler takes care of everything that would differentiate a “procedure” from a “procedure of object” or a “reference to procedure” in Delphi.

Usage

When you need to draw the buffered image, you can use one of its Draw() or DrawScaled() methods, basically telling it to redraw itself on another canvas. The available methods are currently:

// top-left anchor
procedure Draw(dest : TW3Canvas; x, y : Float); overload;
procedure Draw(dest : TW3Canvas; x, y : Float; const sourceRect : TRect); overload;

// center anchor
procedure DrawScaled(dest : TW3Canvas; x, y, scale : Float);

The first two are top-left anchored, ie. the x & y are the top-left of where the image will be drawn, and you can optionally specify a sourceRect, so that only part of your buffered image will be drawn (note that for tiled images, the w3SpriteSheet class is preferable, it offers more features like rotations and automatic sourceRect computations)

For DrawScaled, the x & y are the center of where the image will be drawn.

When you need the image invalidated, you just call it’s Invalidate method, then the next Draw call will first invoke OnRedraw. You can also use the Prepare method to have OnRedraw be invoked at a time of your choosing instead.

Last thing, if you need to draw with alpha-transparency (f.i. to fade-in or out), just use the GlobalAlpha property of the target canvas, f.i.:

myCanvas.GlobalAlpha:=0.5;  // 50% opacity
bufferedImage.Draw(myCanvas, 50, 50); // drawn at top, left = 50,50
myCanvas.GlobalAlpha:=1;  // restore 100% opacity

That’s about it! Now you can efficiently make use of custom dynamic elements, such as multi-layered text with shadow, that would otherwise be too complex to redraw from scratch at each frame!

Tips , , ,

Taming HTML5 verlets with Object Pascal

September 19th, 2011

Click the image below for a little real-time verlet integration animation.

And the source code for that little bit of HTML5 animation is Object Pascal, Delphi Web Script flavor, and is posted below.

It shows off the inline method implementations (you can use classic implementation style, and if that code were in a unit, you would have to), along with the extended Variant syntax. If you look at the HTML source, it also shows off output from an early version of the obfuscator/minifier.

FireFox6 and IE9 handle it fairly well, but Chrome just laughs at it and taunts you to bring it on. Runs on all major mobile platform too, at a reduced framerate of course, but not in slide-show mode (and on Android, FireFox Mobile is king of the hill).

<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
<canvas id="canvas" width="300" height="400"></canvas>
<script>
<%pas2js

type 
   TVerletPoint = class
      private
         FX, FY : Float;
         FOldX, FOldY : Float;

      public
         procedure SetPosition(aX, aY : Float);
         begin
            FX := aX; FOldX := aX;
            FY := aY; FOldY := aY;
         end;

         constructor Create(aX, aY : Float);
         begin
            SetPosition(aX, aY);
         end;

         procedure Update;
         begin
            var dx := FX - FOldX;
            var dy := FY - FOldY;
            FOldX := FX;
            FOldY := FY;
            FX := FX + dx;
            FY := FY + dy;
         end;

         function DistanceTo(p : TVerletPoint) : Float;
         begin
            Result := Sqrt(Sqr(p.FX-FX)+Sqr(p.FY-FY));
         end;

         property X : Float read FX write FX;
         property Y : Float read FY write FY;
   end;

type
    TVerletStick = class
      private
         FDistance : Float;
         FA, FB : TVerletPoint;

      public
         constructor Create(pA, pB : TVerletPoint);
         begin
            FA := pA;
            FB := pB;
            FDistance := pA.DistanceTo(pB);
         end;

         procedure Apply;
         begin
            var dx := FA.X - FB.X;
            var dy := FA.Y - FB.Y;
            var d := Sqrt(dx*dx+dy*dy);
            var f := (d - FDistance) * 0.5 / d;
            FA.X := FA.X - dx * f;
            FB.X := FB.X + dx * f;
            FA.Y := FA.Y - dy * f;
            FB.Y := FB.Y + dy * f;
         end;

         property A : TVerletPoint read FA;
         property B : TVerletPoint read FB;
   end;

const cCOLS = 30;
const cROWS = 25;
const cSPACING = 10;
var points : array of TVerletPoint;
var sticks : array of TVerletStick;

procedure InitGrid;
var
   r, c : Integer;
   point : TVerletPoint;
begin
   for r:=0 to cROWS-1 do begin
      for c:=0 to cCOLS-1 do begin
         point:=new TVerletPoint(c*cSPACING, r*cSPACING);
         points.Add(point);
         if c>0 then
            sticks.Add(new TVerletStick(points[r*cCOLS+c-1], point));
         if r>0 then
            sticks.Add(new TVerletStick(point, points[(r-1)*cCOLS+c]));
      end;
   end;
end;

procedure UpdateGrid(canvas : Variant);
var
   i, j : Integer;
   stick : TVerletStick;
begin
   for i:=0 to points.High do begin
      points[i].Y := points[i].Y + 0.1; // gravity
      points[i].Update;
   end;
   for i:=1 to 3 do begin
      points[0].X := 0;
      points[0].Y := 0;
      points[cCOLS-1].X := (cCOLS-1)*cSPACING;
      points[cCOLS-1].Y := 0;
      for j:=0 to sticks.High do begin
         sticks[j].Apply;
      end;
   end;

   canvas.clearRect(0, 0, 300, 400);
   canvas.beginPath();
   for i:=0 to sticks.High do begin
      stick:=sticks[i];
      canvas.moveTo(stick.A.X, stick.A.Y);
      canvas.lineTo(stick.B.X, stick.B.Y);
   end;
   canvas.stroke();
end;

InitGrid();

procedure Animate(stepFunc : procedure(canvas : Variant)); external;

Animate(UpdateGrid);

%>

var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
canvas.strokeStyle = "black";
canvas.lineWidth = 0.8;

function Animate(f) {
    setInterval(
        function () {
            f(context);
        },
        10);
}

</script>

Source loosely based on Alex Nino’s.

Tips , ,