Recent

Author Topic: Best way to draw a moving line on a Timage (Analog VU Meter)  (Read 18983 times)

Zittergie

  • Full Member
  • ***
  • Posts: 114
    • XiX Music Player
Best way to draw a moving line on a Timage (Analog VU Meter)
« on: January 17, 2013, 06:29:48 pm »
Hi,

Setting:  Lazarus version 1.1 - (revision 39153) - GTK2
OS: Linux Mint 14 (Also Linux ARM, Windows & MacOS X)

I am using the following code to make a Analog VU Meter:

Code: [Select]
VuImage.Picture.Bitmap:=FormGui.ImageVu1.Picture.Bitmap;
VuImage.Picture.Bitmap.Canvas.Pen.Color := clSilver;
VuImage.Picture.Bitmap.Canvas.Pen.Width:=2;
VuImage.Picture.Bitmap.Canvas.Line(94, 94, y, z);

This is called in a Timer.  Everything works fine, no flicker.  But I think that it is also the slowest way to draw the VU Meter.
If I use this code on ARM Linux (Raspberry Pi) it is not fluent (too slow)

I have tried with a Paintbox over the VuImage.Picture, but than I get constant flicker.

What is the best solution to draw this? (but it has to be compatible with Linux x86, Linux ARM, Windows & MacOS X.)
Should I use BGRABitmap ?
Be the difference that makes a difference

typo

  • Hero Member
  • *****
  • Posts: 3051
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #1 on: January 17, 2013, 06:34:13 pm »
Use the sprite technique, draw each portion on a hidden bitmap and then draw the result to the image.

User137

  • Hero Member
  • *****
  • Posts: 1791
    • Nxpascal home
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #2 on: January 18, 2013, 12:16:21 am »
I have tried with a Paintbox over the VuImage.Picture, but than I get constant flicker.
Have a buffer: TBitmap where you Draw() the background bitmap, and then line for meter. Then Draw() to the PaintBox with 1 command:
Code: [Select]
PaintBox1.Canvas.Draw(0, 0, buffer);Or something like that.

Blaazen

  • Hero Member
  • *****
  • Posts: 3241
  • POKE 54296,15
    • Eye-Candy Controls
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #3 on: January 18, 2013, 12:44:48 am »
There is property:
Code: [Select]
DoubleBuffered:=True;It should reduce the flicker.
Lazarus 2.3.0 (rev main-2_3-2863...) FPC 3.3.1 x86_64-linux-qt Chakra, Qt 4.8.7/5.13.2, Plasma 5.17.3
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.21

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

circular

  • Hero Member
  • *****
  • Posts: 4471
    • Personal webpage
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #4 on: January 18, 2013, 02:10:39 am »
Yes, it's more about buffering and also background erasing. You do not need BGRABitmap here unless you want to draw an antialiased line or a semi-transparent line.

To avoid background erasing, if you draw directly on the form, you can add this to your form class :
Code: [Select]
    procedure WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
And in the procedure, leave blank.
Conscience is the debugger of the mind

jmpessoa

  • Hero Member
  • *****
  • Posts: 2330
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #5 on: January 18, 2013, 03:19:59 am »
Hi Zittergie, in last post on other thread

http://www.lazarus.freepascal.org/index.php/topic,19468.0.html

I put a file "tfpcolorbridgepropertyeditor.rar",  with a infrastructure for drawing "offscreen", in fact a
fcl-image wraper... I think it may be useful for your question...

Greetings...

« Last Edit: January 18, 2013, 03:23:42 am by jmpessoa »
Lamw: Lazarus Android Module Wizard
https://github.com/jmpessoa/lazandroidmodulewizard

Zittergie

  • Full Member
  • ***
  • Posts: 114
    • XiX Music Player
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #6 on: January 18, 2013, 09:42:27 pm »
Replaced code with

Code: [Select]
Buffer:=TImage.Create(Self);

Code: [Select]
if vutheme=1 then Buffer.Picture.Bitmap:=FormGui.ImageVU1.Picture.Bitmap
             else Buffer.Picture.Bitmap:=FormGui.ImageVU2.Picture.Bitmap;
buffer.Picture.Bitmap.Canvas.Pen.Color := clSilver;
buffer.Picture.Bitmap.Canvas.Pen.Width:=2;
buffer.Picture.Bitmap.Canvas.Line(94, 94, y, z);
PaintBox1.Canvas.Draw(0,0,buffer.Picture.Bitmap); 

this works the same as the code before.  Don't know if this is a better way, yet have to test it in ARM Linux on the Raspberry Pi.
« Last Edit: January 18, 2013, 10:18:27 pm by Zittergie »
Be the difference that makes a difference

User137

  • Hero Member
  • *****
  • Posts: 1791
    • Nxpascal home
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #7 on: January 19, 2013, 01:01:56 am »
Note then, that TImage is visual component. There will never be any use for hidden TImage component, that's just waste of resources. Simple buffer is manually created TBitmap...

..Or that DoubleBuffered property that others mentioned, then you didn't have to code the manual buffering at all. I didn't mention it at first, because i don't know if it's reliable on all widgetsets. I had some weird experiences with it on Delphi years back.

B4Z1l3

  • New Member
  • *
  • Posts: 12
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #8 on: January 19, 2013, 01:35:05 am »
Using the standard drawing methods it would be better to precompute let's say 64 state of your VU, then when you need to paint the GUI, you draw the right bitmap (among the 64 avalaible) in a Rect...
So this approach is: "dont' bother about drawing a line, put a bitmap". ;)
And as you are using TImage.Canvas then just give up with painting, it'll be ugly, aliased, etc...
Just Draw a pre-computed state of a bitmap on the canvas. For audio stuff there is knobman, a soft which can help into rendering "N" states of a control in a bitmap.
« Last Edit: January 19, 2013, 01:46:16 am by B4Z1l3 »

Dick, from the internet

  • Full Member
  • ***
  • Posts: 198
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #9 on: January 19, 2013, 03:11:17 am »
I used a solution similar as suggested by B4Z1l3 on a compass project some time ago.  I just transferred it to my RPi and re-compiled; it executed as it should with no screen flicker (that I could tell).

I used a TPaintBox to display a 400X400 gif image of a compass face.  I then (painstakingly!) entered the X,Y coordinates for 0 through 359 degrees for a single line into a TCollection data structure to save to disc for later retrieval.

  The line moves about the image similar to the second hand on a clock using a TTimer updating the PaintBox paint method as such:
Code: [Select]
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  bGroundPic.Canvas.Draw(bGroundX,bGroundY,bGroundPic);
  PaintBox1.Canvas.Draw(0, 0, bGroundPic);
  PaintBox1.Canvas.Brush.Style := bsClear;
  PaintBox1.Canvas.Pen.Color := clGreen;
  PaintBox1.Canvas.Line(TPArray[0].X,TPArray[0].Y,TPArray[1].X,TPArray[1].Y);
  PaintBox1.Canvas.Pen.Color := clRed;
  PaintBox1.Canvas.Line(TPArray[0].X,TPArray[0].Y,TPArray[2].X,TPArray[2].Y);
  PaintBox1.Canvas.Line(TPArray[0].X,TPArray[0].Y,TPArray[3].X,TPArray[3].Y);
//  Application.ProcessMessages;                               //<<-- this really slowed things down
end;
 

Results should prove to be at least as good for your VUMeter.

  regards,
     geno

typo

  • Hero Member
  • *****
  • Posts: 3051
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #10 on: January 19, 2013, 07:53:33 am »
I agree. B4Z1l3's solution should improve speed, which is also a game technique.

It's probably better to keep everything in memory instead of loading from file, load them all first.
« Last Edit: January 19, 2013, 08:02:55 am by typo »

jmpessoa

  • Hero Member
  • *****
  • Posts: 2330
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #11 on: January 19, 2013, 08:11:45 am »
Here is a solution from simplifying and adapting  my component TFPCustomCanvasBridge ..and  using the  "buffer" technique....



Code: [Select]
unit FPCustomCanvasBridge;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, FPImage,
  FPCanvas, FPImgCanv, fpreadpng;


(*
From: http://wiki.freepascal.org/Developing_with_Graphics

"You can draw images which won't be displayed in the screen without the LCL, by just using fcl-image directly.
For example a program running on a webserver without X11 could benefit from not having a visual library
as a dependency.
FPImage (alias fcl-image) is a very generic image and drawing library written completely in pascal.
In fact the LCL uses FPImage too for all the loading and saving from/to files and implements the
drawing function through calls to the widgetset (winapi, gtk, carbon, ...).
Fcl-image on the other hand also has drawing routines.
For more information, please read the article about fcl-image" (http://wiki.freepascal.org/fcl-image).

*)


type
  TFPCustomCanvasBridge = class
    { Private declarations }
  private
    FImgMem: TFPCustomImage;
    FReaderPNG: TFPCustomImageReader;
  protected
    { Protected declarations }
  public
    { Public declarations }
    Canvas: TFPCustomCanvas;
    procedure CopyToCanvas(ShiftX,ShiftY: integer; ACanvas: TFPCustomCanvas);
    procedure CopyFromCanvas(ACanvas: TFPCustomCanvas);
    constructor Create(W, H: integer; backgroundImageFile: string);
    destructor Destroy; override;
  end;

implementation

constructor TFPCustomCanvasBridge.Create(W, H: integer; backgroundImageFile: string);
begin
    FReaderPNG:= TFPReaderPNG.Create;
    FImgMem:= TFPMemoryImage.Create(W,H);
    FImgMem.UsePalette:= False;
    if backgroundImageFile <> '' then  //TODO... "Sanity" code here!!!
       FImgMem.LoadFromFile(backgroundImageFile, FReaderPNG);

    Canvas := TFPImageCanvas.Create(FImgMem);

    if backgroundImageFile = '' then Canvas.Rectangle(0,0,W,H);
end;

destructor TFPCustomCanvasBridge.Destroy;
begin
   Canvas.Free;
   FImgMem.Free;
   FReaderPNG.Free;
   inherited Destroy;
end;

procedure TFPCustomCanvasBridge.CopyToCanvas(ShiftX,ShiftY: integer; ACanvas: TFPCustomCanvas);
begin
   ACanvas.CopyRect(ShiftX, ShiftY,Canvas, Rect(0,0,Canvas.Width,Canvas.Height));
end;

procedure TFPCustomCanvasBridge.CopyFromCanvas(ACanvas: TFPCustomCanvas);
begin
   Canvas.CopyRect(0,0,ACanvas,Rect(0,0,ACanvas.Width,ACanvas.Height));
end;

end.

App Form code:
Code: [Select]
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics,
  Dialogs, ExtCtrls, ComCtrls, StdCtrls, FPImage, FPCustomCanvasBridge;

type

  { TForm1 }

  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    Panel1: TPanel;
    Panel2: TPanel;
    StatusBar1: TStatusBar;
    TrackBar1: TTrackBar;
    procedure FormActivate(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
  private
    { private declarations }
    MainCanvas: TFPCustomCanvasBridge;
    BackupCanvas: TFPCustomCanvasBridge;
    DraftCanvas: TFPCustomCanvasBridge;
    InDrawingMode: boolean;
    pX: integer;
    pY: integer;
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
   //create canvas objects
   MainCanvas:= TFPCustomCanvasBridge.Create(PaintBox1.Width, PaintBox1.Height, 'VU_Meter01.png');  //Main canvas...
   BackupCanvas:= TFPCustomCanvasBridge.Create(PaintBox1.Width, PaintBox1.Height, '');  //Backup Canvas..
   DraftCanvas:= TFPCustomCanvasBridge.Create(PaintBox1.Width, PaintBox1.Height, '');  //Draft Canvas..

   //inits....
   PY:= 20;
   BackupCanvas.CopyFromCanvas(MainCanvas.Canvas);

   DraftCanvas.Canvas.Pen.Width:= 3;
   DraftCanvas.Canvas.Pen.FPColor:= colLime;
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
    InDrawingMode:= True;
end;

procedure TForm1.TrackBar1Change(Sender: TObject);
begin
    if InDrawingMode then
    begin
       DraftCanvas.CopyFromCanvas(BackupCanvas.Canvas);//copy backup...

       if TrackBar1.Position = 0 {Min} then PX:= 5
       else if TrackBar1.Position = TrackBar1.Max {20} then PX:= PaintBox1.Width-5
       else pX:= Round(TrackBar1.Position*(PaintBox1.Width-10)/TrackBar1.Max);

       //dinamyc draw on draft canvas...
       DraftCanvas.Canvas.Line(Round(PaintBox1.Width/2),PaintBox1.Height, PX, PY);

       //No flick.... updade Main Canvas
       MainCanvas.CopyFromCanvas(DraftCanvas.Canvas);

       PaintBox1.Invalidate; //call PaintBoxPaint..
    end;
end;

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
    if InDrawingMode then
      MainCanvas.CopyToCanvas(0,0,PaintBox1.Canvas);
end;

end.

Follows attached the complete project ...

Greetings...

Lamw: Lazarus Android Module Wizard
https://github.com/jmpessoa/lazandroidmodulewizard

Zittergie

  • Full Member
  • ***
  • Posts: 114
    • XiX Music Player
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #12 on: January 20, 2013, 11:35:17 am »
Thanks to all

I think I go for the easiest way for now and use the example from jmpessoa and see how it performs on the Pi.
Going to look for somemore info on how to use a canvas already on a Form and add a procedure.
(Think I'll start with a good read @ http://wiki.freepascal.org/Developing_with_Graphics)

I let you know what I came up with.
Be the difference that makes a difference

Chronos

  • Sr. Member
  • ****
  • Posts: 256
    • PascalClassLibrary
Re: Best way to draw a moving line on a Timage (Analog VU Meter)
« Reply #13 on: January 20, 2013, 11:47:10 pm »
Here you have just another simple example application. Just paintbox over timage with doublebuffering. You should avoid to copy entire bitmap by yourself if possible. Block data manipulation is time consuming. Some part can do operation system with optimized manipulation of bitmaps in their native bitmap format.
If you have to copy bitmap by yourself then it is good idea to limit redrawing only necessary area and not entire image area.

Code: [Select]
unit UFormMain;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  ComCtrls, StdCtrls, LMessages;

type

  { TForm1 }

  TForm1 = class(TForm)
    CheckBox1: TCheckBox;
    CheckBox2: TCheckBox;
    Image1: TImage;
    PaintBox1: TPaintBox;
    TrackBar1: TTrackBar;
    procedure CheckBox1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
  private
    procedure WMEraseBkgnd(var Message: TLMEraseBkgnd); message LM_ERASEBKGND;
  public
    Angle: Double;
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  Angle := TrackBar1.Position / TrackBar1.Max * 70 - 125;
  PaintBox1.Canvas.Pen.Style := psSolid;
  PaintBox1.Canvas.Pen.Color := clBlack;
  PaintBox1.Canvas.Line(90, 140, 90 + Round(Cos(Angle / 180 * Pi) * 130),
    140 + Round(Sin(Angle / 180 * Pi) * 130));
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  DoubleBuffered := CheckBox1.Checked;
end;

procedure TForm1.TrackBar1Change(Sender: TObject);
begin
  PaintBox1.Repaint;
end;

procedure TForm1.WMEraseBkgnd(var Message: TLMEraseBkgnd);
begin
  if CheckBox2.Checked then inherited WMEraseBkgnd(Message);
end;

end.

 

TinyPortal © 2005-2018