Best way to draw a moving line on a Timage (Analog VU Meter) lazarus / 39-Graphics ---------------------------------------------------------------------- Zittergie · 2013-01-17 17:29 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 ? ---------------------------------------------------------------------- typo · 2013-01-17 17:34 Use the sprite technique, draw each portion on a hidden bitmap and then draw the result to the image. ---------------------------------------------------------------------- User137 · 2013-01-17 23:16 Quote from: Zittergie on January 17, 2013, 06:29:48 pmI 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 · 2013-01-17 23:44 There is property: Code: [Select]DoubleBuffered:=True;It should reduce the flicker. ---------------------------------------------------------------------- circular · 2013-01-18 01:10 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. ---------------------------------------------------------------------- jmpessoa · 2013-01-18 02:19 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... ---------------------------------------------------------------------- Zittergie · 2013-01-18 20:42 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. ---------------------------------------------------------------------- User137 · 2013-01-19 00:01 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 · 2013-01-19 00:35 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. ---------------------------------------------------------------------- Dick, from the internet · 2013-01-19 02:11 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 · 2013-01-19 06:53 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. ---------------------------------------------------------------------- jmpessoa · 2013-01-19 07:11 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... ---------------------------------------------------------------------- Zittergie · 2013-01-20 10:35 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. ---------------------------------------------------------------------- Chronos · 2013-01-20 22:47 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.