Recent

Author Topic: Analog clock created using BGRABitmap  (Read 24085 times)

digeo

  • Jr. Member
  • **
  • Posts: 54
    • Digeotek
Analog clock created using BGRABitmap
« on: January 27, 2013, 08:02:47 pm »
I have created an analog clock for my app using the BGRABitmap methods. Thought I should share. I got the idea from http://libregraphicsworld.org/blog/entry/drawing-mac-like-clock-in-inkscape.

I basically redraw the clock using the redraw event from a BGRAVirtualScreen control. And then a timer to increment the clock.

The form can be resized during runtime and the clock will be redrawn using built in scaling and referencing the ellipse radius.

An improvement can be to add glare on glass.

Code: [Select]
procedure TForm1.vsClockRedraw(Sender: TObject; Bitmap: TBGRABitmap);
var
  frame, face, arm, dot, clock: TBGRABitmap;
  w, h, r, A, Xo, Yo, X, Y: integer;
  Xs, Ys, Xm, Ym, Xh, Yh: integer;
  tt: TTime;
  th, tm, ts: integer;
begin
  w := (sender as TBGRAVirtualScreen).Width;
  h := (sender as TBGRAVirtualScreen).Height;

  { Set center point }
  Xo := w div 2;
  Yo := h div 2;

  r := yo;

  if xo > yo then
    r := yo;

  if xo < yo then
    r := xo;

  tt := Time;
  th := StrToInt(Copy(FormatDateTime('hh AM/PM', tt), 1, 2));
  tm := StrToInt(FormatDateTime('nn', tt));
  ts := StrToInt(FormatDateTime('ss', tt));

  { Set coordinates for seconds }
  Xs := Xo + Round(r * 0.78 * Sin(ts * 6 * Pi / 180));
  Ys := Yo - Round(r * 0.78 * Cos(ts * 6 * Pi / 180));

  { Set coordinates for minutes }
  Xm := Xo + Round(r * 0.68 * Sin(tm * 6 * Pi / 180));
  Ym := Yo - Round(r * 0.68 * Cos(tm * 6 * Pi / 180));

  { Set coordinates for hours }
  Xh := Xo + Round(r * 0.50 * Sin((th * 30 + tm / 2) * Pi / 180));
  Yh := Yo - Round(r * 0.50 * Cos((th * 30 + tm / 2) * Pi / 180));

  // Draw clock frame
  clock := TBGRABitmap.Create(w, h);

  clock.FillEllipseAntialias(Xo, Yo, r * 0.99, r * 0.99, BGRA(175, 175, 175));
  clock.FillEllipseAntialias(Xo, Yo, r * 0.98, r * 0.98, BGRA(245, 245, 245));
  clock.FillEllipseAntialias(Xo, Yo, r * 0.90, r * 0.90, BGRA(175, 175, 175));
  clock.FillEllipseLinearColorAntialias(Xo, Yo, r * 0.88, r * 0.88, BGRA(0, 58, 81), BGRA(2, 94, 131));

  // Draw clock face
  for A := 0 to 12 do
  begin
    X := Xo + Round(r * 0.80 * Sin(30 * A * Pi / 180));
    Y := Yo - Round(r * 0.80 * Cos(30 * A * Pi / 180));
    clock.EllipseAntialias(x, y, r * 0.015, r * 0.015, BGRA(255, 255, 255, 200), 1, BGRA(255, 255, 255, 200));
  end;

  // Draw time hands
  clock.DrawLineAntialias(xo, yo, xs, ys, BGRA(255, 0, 0, 150), 2 * (R * 0.01));
  clock.DrawLineAntialias(xo, yo, xm, ym, BGRA(245, 245, 245, 150), 3 * (R * 0.01));
  clock.DrawLineAntialias(xo, yo, xh, yh, BGRA(245, 245, 245, 140), 6 * (R * 0.01));
  clock.DrawLineAntialias(xo, yo, xh, yh, BGRA(2, 94, 131), 4 * (R * 0.01));

  // Draw clock centre dot
  clock.EllipseAntialias(Xo, Yo, 4 * (R * 0.01), 4 * (R * 0.01), BGRA(245, 245, 245, 255), 2 * (R * 0.01), BGRA(210, 210, 210, 255));
  Bitmap.PutImage(0, 0, clock, dmDrawWithTransparency);

  clock.Free;

end;
« Last Edit: January 27, 2013, 08:05:47 pm by digeo »

User137

  • Hero Member
  • *****
  • Posts: 1791
    • Nxpascal home
Re: Analog clock created using BGRABitmap
« Reply #1 on: January 27, 2013, 09:00:33 pm »
Just small tip, you don't need to handle strings when it comes to time. It's possible to decode it quickly:
Code: [Select]
var hour, minute, second, msec: word;
begin
  decodetime(time, hour, minute, second, msec);

digeo

  • Jr. Member
  • **
  • Posts: 54
    • Digeotek
Re: Analog clock created using BGRABitmap
« Reply #2 on: January 27, 2013, 09:55:12 pm »
Thanks User137

Have implemented that. Also implemented some text on the face.

new code
Code: [Select]
unit umain;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, BGRAVirtualScreen, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  BGRABitmap, BGRABitmapTypes, bgrasamples, BGRATextFX;

type

  { TForm1 }

  TForm1 = class(TForm)
    vsClock : TBGRAVirtualScreen;
    Timer1: TTimer;
    procedure Timer1Timer(Sender: TObject);
    procedure vsClockRedraw(Sender: TObject; Bitmap: TBGRABitmap);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.vsClockRedraw(Sender: TObject; Bitmap: TBGRABitmap);
var
  clock: TBGRABitmap;
  txt : TBGRACustomBitmap;
  w, h, r, A, Xo, Yo, X, Y: integer;
  Xs, Ys, Xm, Ym, Xh, Yh: integer;
  th, tm, ts, tn: Word;
begin
  w := (sender as TBGRAVirtualScreen).Width;
  h := (sender as TBGRAVirtualScreen).Height;

  { Set center point }
  Xo := w div 2;
  Yo := h div 2;

  r := yo;

  if xo > yo then
    r := yo;

  if xo < yo then
    r := xo;

  decodetime(Time, th, tm, ts, tn);

  { Set coordinates for seconds }
  Xs := Xo + Round(r * 0.78 * Sin(ts * 6 * Pi / 180));
  Ys := Yo - Round(r * 0.78 * Cos(ts * 6 * Pi / 180));

  { Set coordinates for minutes }
  Xm := Xo + Round(r * 0.68 * Sin(tm * 6 * Pi / 180));
  Ym := Yo - Round(r * 0.68 * Cos(tm * 6 * Pi / 180));

  { Set coordinates for hours }
  Xh := Xo + Round(r * 0.50 * Sin((th * 30 + tm / 2) * Pi / 180));
  Yh := Yo - Round(r * 0.50 * Cos((th * 30 + tm / 2) * Pi / 180));

  // Draw clock frame
  clock := TBGRABitmap.Create(w, h);

  clock.FillEllipseAntialias(Xo, Yo, r * 0.99, r * 0.99, BGRA(175, 175, 175));
  clock.FillEllipseAntialias(Xo, Yo, r * 0.98, r * 0.98, BGRA(245, 245, 245));
  clock.FillEllipseAntialias(Xo, Yo, r * 0.90, r * 0.90, BGRA(175, 175, 175));
  clock.FillEllipseLinearColorAntialias(Xo, Yo, r * 0.88, r * 0.88, BGRA(0, 58, 81), BGRA(2, 94, 131));

  // Draw clock face
  for A := 1 to 12 do
  begin
    X := Xo + Round(r * 0.80 * Sin(30 * A * Pi / 180));
    Y := Yo - Round(r * 0.80 * Cos(30 * A * Pi / 180));
    clock.EllipseAntialias(x, y, r * 0.015, r * 0.015, BGRA(255, 255, 255, 200), 1, BGRA(255, 255, 255, 200));
  end;

  // Draw text
  txt := TextShadow(w, h, 'www.Digeotek.com', 7 * trunc(r * 0.02), ColorToBGRA(clWhite), BGRA(175, 175, 175), 0, 0, 10, [], 'Calibri');
  clock.PutImage(0, 0 - (r div 3), txt, dmDrawWithTransparency);
  txt.Free;

  // Draw time hands
  clock.DrawLineAntialias(xo, yo, xs, ys, BGRA(255, 0, 0), 2 * (R * 0.01));
  clock.DrawLineAntialias(xo, yo, xm, ym, BGRA(245, 245, 245), 3 * (R * 0.01));
  clock.DrawLineAntialias(xo, yo, xh, yh, BGRA(245, 245, 245), 7 * (R * 0.01));
  clock.DrawLineAntialias(xo, yo, xh, yh, BGRA(2, 94, 131), 4 * (R * 0.01));

  // Draw clock centre dot
  clock.EllipseAntialias(Xo, Yo, 4 * (R * 0.01), 4 * (R * 0.01), BGRA(245, 245, 245, 255), 2 * (R * 0.01), BGRA(210, 210, 210, 255));
  Bitmap.PutImage(0, 0, clock, dmDrawWithTransparency);

  clock.Free;

end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  vsClock.RedrawBitmap;
end;

end.

lainz

  • Guest
Re: Analog clock created using BGRABitmap
« Reply #3 on: January 27, 2013, 10:39:18 pm »
I like it!

Try to add the lights
(Maybe this helps http://wiki.lazarus.freepascal.org/BGRABitmap_tutorial_5)

and the numbers.

Also you can add the gradients, look in BGRAGradients unit.

circular

  • Hero Member
  • *****
  • Posts: 4455
    • Personal webpage
Re: Analog clock created using BGRABitmap
« Reply #4 on: January 28, 2013, 09:16:14 pm »
Note that you do not need to create a separate clock bitmap, you can draw directly on Bitmap variable.
Conscience is the debugger of the mind

digeo

  • Jr. Member
  • **
  • Posts: 54
    • Digeotek
Re: Analog clock created using BGRABitmap
« Reply #5 on: January 30, 2013, 07:44:01 am »
I have added the numbers and improved the text scaling. Also took out the clock variable and 'refined' some of the formulas.

Next will be to add the gradient for better depth and reflections

Code: [Select]
procedure TForm1.vsClockRedraw(Sender: TObject; Bitmap: TBGRABitmap);
var
  txt: TBGRACustomBitmap;
  w, h, r, A, Xo, Yo, X, Y, Xt, Yt: integer;
  Xs, Ys, Xm, Ym, Xh, Yh: integer;
  th, tm, ts, tn: word;
begin
  w := (Sender as TControl).Width;
  h := (Sender as TControl).Height;

  { Set center point }
  Xo := w div 2;
  Yo := h div 2;

  // Determine radius. If canvas is rectangular then r = shortest length w or h
  r := yo;

  if xo > yo then
    r := yo;

  if xo < yo then
    r := xo;

  // Convert current time to integer values
  decodetime(Time, th, tm, ts, tn);

  { Set coordinates (length of arm) for seconds }
  Xs := Xo + Round(r * 0.78 * Sin(ts * 6 * Pi / 180));
  Ys := Yo - Round(r * 0.78 * Cos(ts * 6 * Pi / 180));

  { Set coordinates (length of arm) for minutes }
  Xm := Xo + Round(r * 0.68 * Sin(tm * 6 * Pi / 180));
  Ym := Yo - Round(r * 0.68 * Cos(tm * 6 * Pi / 180));

  { Set coordinates (length of arm) for hours }
  Xh := Xo + Round(r * 0.50 * Sin((th * 30 + tm / 2) * Pi / 180));
  Yh := Yo - Round(r * 0.50 * Cos((th * 30 + tm / 2) * Pi / 180));

  // Draw Bitmap frame
  Bitmap.FillEllipseAntialias(Xo, Yo, r * 0.99, r * 0.99, BGRA(175, 175, 175));
  Bitmap.FillEllipseAntialias(Xo, Yo, r * 0.98, r * 0.98, BGRA(245, 245, 245));
  Bitmap.FillEllipseAntialias(Xo, Yo, r * 0.90, r * 0.90, BGRA(175, 175, 175));
  Bitmap.FillEllipseLinearColorAntialias(Xo, Yo, r * 0.88, r * 0.88, BGRA(0, 58, 81), BGRA(2, 94, 131));

  // Draw Bitmap face
  for A := 1 to 12 do
  begin
    X := Xo + Round(r * 0.80 * Sin(30 * A * Pi / 180));
    Y := Yo - Round(r * 0.80 * Cos(30 * A * Pi / 180));
    Xt := Xo + Round(r * 0.70 * Sin(30 * A * Pi / 180));
    Yt := Yo - Round(r * 0.70 * Cos(30 * A * Pi / 180));
    Bitmap.EllipseAntialias(x, y, (r * 0.02), (r * 0.02), BGRA(255, 255, 255, 200), 2, BGRA(2, 94, 131));

    Bitmap.FontName := 'Calibri';
    Bitmap.FontHeight := r div 8;
    Bitmap.FontQuality := fqFineAntialiasing;
    Bitmap.TextOut(Xt, Yt - (Bitmap.FontHeight / 1.7), IntToStr(A), BGRA(245, 245, 245), taCenter);
  end;

  // Draw text
  txt := TextShadow(w, h, 'www.Digeotek.com', trunc(r * 0.12), ColorToBGRA(clWhite), BGRABlack, 4, 4, 10, [], 'Calibri');
  Bitmap.BlendImage(0, 0 - (r div 3), txt, boLinearBlend);
  txt.Free;

  // Draw time hands
  Bitmap.DrawLineAntialias(xo, yo, xs, ys, BGRA(255, 0, 0), r * 0.02);
  Bitmap.DrawLineAntialias(xo, yo, xm, ym, BGRA(245, 245, 245), r * 0.03);
  Bitmap.DrawLineAntialias(xo, yo, xh, yh, BGRA(245, 245, 245), r * 0.07);
  Bitmap.DrawLineAntialias(xo, yo, xh, yh, BGRA(2, 94, 131), r * 0.04);

  // Draw Bitmap centre dot
  Bitmap.EllipseAntialias(Xo, Yo, r * 0.04, r * 0.04, BGRA(245, 245, 245, 255), r * 0.02, BGRA(210, 210, 210, 255));
  Bitmap.BlendImage(0, 0, Bitmap, boLinearBlend);

end;

wjackson153

  • Sr. Member
  • ****
  • Posts: 267
Re: Analog clock created using BGRABitmap
« Reply #6 on: January 30, 2013, 07:56:20 am »

Looks good but I think you clock image would look
more profesional looking if your wrapped ,
text around the clock face.

Just a thought :)
Lazarus 1.1 r39490 CT FPC 2.7.1 i386-linux KDE
Linux Mint 14 KDE 4

CaptBill

  • Sr. Member
  • ****
  • Posts: 435
Re: Analog clock created using BGRABitmap
« Reply #7 on: January 30, 2013, 10:07:46 am »
Awesome demonstration of the power and beauty of Lazarus/Fpc coupled with a well made library (BGRAbitmap). What was that? Like 60 lines of code? This is the best demonstration of building something practical I have ever seen, of Lazarus and BGRABitmap, or anything else for that matter.

Impressive show.


circular

  • Hero Member
  • *****
  • Posts: 4455
    • Personal webpage
Re: Analog clock created using BGRABitmap
« Reply #8 on: January 30, 2013, 12:44:10 pm »
Very nice.

digeo, I suppose you can remove the last line (it's drawing the bitmap on itself) :
Code: [Select]
Bitmap.BlendImage(0, 0, Bitmap, boLinearBlend);
Conscience is the debugger of the mind

circular

  • Hero Member
  • *****
  • Posts: 4455
    • Personal webpage
Re: Analog clock created using BGRABitmap
« Reply #9 on: January 30, 2013, 02:32:35 pm »
Here you can draw a shaded border for the clock :
Code: [Select]
uses BGRAGradients;
...
var
   ...
  phong: TPhongShading;
begin
  ...
  // Draw Bitmap frame
  phong := TPhongShading.Create;
  phong.LightPosition := point(0,0);
  phong.DrawSphere(Bitmap, rect(round(Xo-r*0.99),round(Yo-r*0.99),round(Xo+r*0.99)+1,round(Yo+r*0.99)+1),4,BGRA(245, 245, 245));
  phong.Free;
  Bitmap.FillEllipseLinearColorAntialias(Xo, Yo, r * 0.88, r * 0.88, BGRA(0, 58, 81), BGRA(2, 94, 131));
Conscience is the debugger of the mind

digeo

  • Jr. Member
  • **
  • Posts: 54
    • Digeotek
Re: Analog clock created using BGRABitmap
« Reply #10 on: January 30, 2013, 02:35:23 pm »
That looks good yes. Will implement tonight.

digeo

  • Jr. Member
  • **
  • Posts: 54
    • Digeotek
Re: Analog clock created using BGRABitmap
« Reply #11 on: February 14, 2013, 01:45:59 pm »
Updated the to use the phong procedure to achieve rounded look on the outer ring.

Thank you Circular

Code: [Select]
unit umain;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, BGRAVirtualScreen, Forms, Controls,
  Graphics, Dialogs, ExtCtrls,
  BGRABitmap, BGRABitmapTypes, bgrasamples, BGRAButton, BGRATextFX, BGRAGradients;

type

  { TForm1 }

  TForm1 = class(TForm)
    vsClock: TBGRAVirtualScreen;
    Timer1: TTimer;
    procedure Timer1Timer(Sender: TObject);
    procedure vsClockRedraw(Sender: TObject; Bitmap: TBGRABitmap);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.vsClockRedraw(Sender: TObject; Bitmap: TBGRABitmap);
var
  txt: TBGRACustomBitmap;
  w, h, r, A, Xo, Yo, X, Y, Xt, Yt: integer;
  Xs, Ys, Xm, Ym, Xh, Yh: integer;
  th, tm, ts, tn: word;
  phong: TPhongShading;
begin
  w := (Sender as TControl).Width;
  h := (Sender as TControl).Height;

  { Set center point }
  Xo := w div 2;
  Yo := h div 2;

  // Determine radius. If canvas is rectangular then r = shortest length w or h
  r := yo;

  if xo > yo then
    r := yo;

  if xo < yo then
    r := xo;

  // Convert current time to integer values
  decodetime(Time, th, tm, ts, tn);

  { Set coordinates (length of arm) for seconds }
  Xs := Xo + Round(r * 0.78 * Sin(ts * 6 * Pi / 180));
  Ys := Yo - Round(r * 0.78 * Cos(ts * 6 * Pi / 180));

  { Set coordinates (length of arm) for minutes }
  Xm := Xo + Round(r * 0.68 * Sin(tm * 6 * Pi / 180));
  Ym := Yo - Round(r * 0.68 * Cos(tm * 6 * Pi / 180));

  { Set coordinates (length of arm) for hours }
  Xh := Xo + Round(r * 0.50 * Sin((th * 30 + tm / 2) * Pi / 180));
  Yh := Yo - Round(r * 0.50 * Cos((th * 30 + tm / 2) * Pi / 180));

  // Draw Bitmap frame
  Bitmap.FillEllipseAntialias(Xo, Yo, r * 0.99, r * 0.99, BGRA(175, 175, 175));

  // Draw Rounded/RIng type border using shading
  phong := TPhongShading.Create;
  phong.LightPosition := point(Xo, Yo);
  phong.DrawSphere(Bitmap, rect(round(Xo - r * 0.98), round(Yo - r * 0.98), round(Xo + r * 0.98) + 1, round(Yo + r * 0.98) + 1), 4, BGRA(245, 245, 245));
  phong.Free;
  Bitmap.FillEllipseLinearColorAntialias(Xo, Yo, r * 0.88, r * 0.88, BGRA(0, 58, 81), BGRA(2, 94, 131));

  // Draw Face frame
  Bitmap.FillEllipseAntialias(Xo, Yo, r * 0.90, r * 0.90, BGRA(175, 175, 175));

  // Draw face background
  Bitmap.FillEllipseLinearColorAntialias(Xo, Yo, r * 0.88, r * 0.88, BGRA(0, 58, 81), BGRA(2, 94, 131));


  // Draw Bitmap face
  for A := 1 to 12 do
  begin
    X := Xo + Round(r * 0.80 * Sin(30 * A * Pi / 180));
    Y := Yo - Round(r * 0.80 * Cos(30 * A * Pi / 180));
    Xt := Xo + Round(r * 0.70 * Sin(30 * A * Pi / 180));
    Yt := Yo - Round(r * 0.70 * Cos(30 * A * Pi / 180));
    Bitmap.EllipseAntialias(x, y, (r * 0.02), (r * 0.02), BGRA(255, 255, 255, 200),
      2, BGRA(2, 94, 131));

    Bitmap.FontName := 'Calibri';
    Bitmap.FontHeight := r div 8;
    Bitmap.FontQuality := fqFineAntialiasing;
    Bitmap.TextOut(Xt, Yt - (Bitmap.FontHeight / 1.7), IntToStr(A),
      BGRA(245, 245, 245), taCenter);
  end;

  // Draw text
  txt := TextShadow(w, h, 'www.Digeotek.com', trunc(r * 0.12),
    ColorToBGRA(clWhite), BGRABlack, 4, 4, 10, [], 'Calibri');
  Bitmap.BlendImage(0, 0 - (r div 3), txt, boLinearBlend);
  txt.Free;

  // Draw time hands
  Bitmap.DrawLineAntialias(xo, yo, xs, ys, BGRA(255, 0, 0), r * 0.02);
  Bitmap.DrawLineAntialias(xo, yo, xm, ym, BGRA(245, 245, 245), r * 0.03);
  Bitmap.DrawLineAntialias(xo, yo, xh, yh, BGRA(245, 245, 245), r * 0.07);
  Bitmap.DrawLineAntialias(xo, yo, xh, yh, BGRA(2, 94, 131), r * 0.04);

  // Draw Bitmap centre dot
  Bitmap.EllipseAntialias(Xo, Yo, r * 0.04, r * 0.04, BGRA(245, 245, 245, 255),
    r * 0.02, BGRA(210, 210, 210, 255));
  Bitmap.BlendImage(0, 0, Bitmap, boLinearBlend);

end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  vsClock.RedrawBitmap;
end;

end.

picstart

  • Full Member
  • ***
  • Posts: 236
Re: Analog clock created using BGRABitmap
« Reply #12 on: February 14, 2013, 09:00:09 pm »
This code does work reasonably well on a 3 ghz windows 7 pro 64bit. Reasonably well means the redraw is sub second. I know it sounds futuristic but I thought I'd have a try at porting to a raspberry pi (debian wheezy). It is actually possible to move source code to the pi and compile natively on the pi.
This was done to further test the BRGA components previously install natively on the pi ( BGRA source ported and lazarus compiled and rebuilt with the BGRA components added to the palette).
The only issue is now the second hand moves in 7 to 8 second intervals due to the huge resources absorbed by redraw. This isn't a complaint just an observation that look and feel can bring a 700 mhz ARM processor to its knees. If lazarus moves in the direction of gigahertz desktops and gigabyte ram it could quickly become irrelevant as the world moves to handheld devices.
On the sunny side I ran a floating point division test on the pi using lazarus double ( aka float) and it was getting very close to 1 megaflop per second.
The pi can handle HD video without missing a beat very impressive. Apart from the sloooow compile of lazarus on the pi it is very nice to see that lazarus will work natively on that platform.

circular

  • Hero Member
  • *****
  • Posts: 4455
    • Personal webpage
Re: Analog clock created using BGRABitmap
« Reply #13 on: February 14, 2013, 10:32:39 pm »
Well obviously, it can be optimized by computed a background only when the window size change, and drawing moving parts only.
Conscience is the debugger of the mind

digeo

  • Jr. Member
  • **
  • Posts: 54
    • Digeotek
Re: Analog clock created using BGRABitmap
« Reply #14 on: February 15, 2013, 02:48:17 pm »
That is actually how I have applied it n my app. Create background ( clock face and frame etc etc) on start up. then on the timer just clear moving parts and redraw. redraw background when resized.

 

TinyPortal © 2005-2018