The Lazarus TImage.Canvas is too slow lazarus / 39-Graphics ---------------------------------------------------------------------- QuinnMartin · 2023-02-16 19:57 I have been trying to write a simple program (below) that draws a sine wave.  However it's slow... it takes 50 milliseconds to draw a single wave, even with optimization changed to -O3.  This is very simple mathematics that should be trivial on a modern CPU.  It's plotting a series of 1200 line segments (MoveTo) to draw the sine wave, but still it should really not take half a second considering there are all kinds of games that draw more complex renders (even outside of OpenGL and DirectX) much more quickly. I can't accomplish what I want to do because I'm trying to draw 20 of these waves, and that adds up to very sluggish performance when you're trying to watch the results of moving the TTrackBar in realtime. Any thoughts how I can speed this up?  Or is it just not possible in FreePascal?  I tried BitBlt to buffer clearing the image but it was only 5% faster.  I'm not sure if I'm just writing bad code or if there are other settings I need to look at. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  // NOTES: The code below has been modified to just draw a single wave and is triggered by a TTrackBar that changes the wavelength; it also has some code to time the operation and send it to a TMemo. function TForm1.DrawWave : integer;var  xx, yy, f1, verticaloffset, yy1, yy2, centerratio, r1, r2, r3 : real;  s1 : string;begin  Image1.canvas.rect angle(0,0,Image1.Canvas.Width,Image1.Canvas.Height);   r1 := time*24*60*60;   verticaloffset := 300;   f1 := Wave1Frequency.position;  f1 := f1/10;  if (f1 > 0) then    begin    yy := 300;   // was 0      while (yy < 301) do  // was 600        begin          yy := yy+10;          xx := 1;          while (xx < 1200) do            begin              xx := xx+1;              yy1 := (sin((xx/f1)*pi/180) * 100);              yy2 := (sin(((xx+1)/f1)*pi/180) * 100);              centerratio := 1 - (abs(yy-verticaloffset))/verticaloffset;              yy1 := yy1*centerratio;              yy2 := yy2*centerratio;              yy1 := yy1+yy;              yy2 := yy2+yy;              Image1.Canvas.MoveTo(round(xx),round(yy1));              Image1.Canvas.LineTo(round(xx+1),round(yy2));            end;        end;    end;  r2 := time*24*60*60;  r3 := (r2-r1)*1000;  str(r3:0:0,s1);   Memo1.Lines.Add('Time '+s1+' ms');end; ---------------------------------------------------------------------- howardpc · 2023-02-16 20:45 Try something like this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.DrawWave;const  PiOver180 = Pi/180;var  frequency: Single = 1;  verticalOffset: Integer = 300;  xx: SizeInt = 1;  yy: SizeInt = 300;  yy1, yy2, centerRatio: Single;begin  Image1.Canvas.Rectangle(Image1.ClientRect);  if frequency <= 0 then Exit;   while yy < 301 do    begin      Inc(yy, 10);      while xx < 1200 do        begin          Inc(xx);          yy1 := (Sin((xx/frequency)*PiOver180) * 100);          yy2 := (sin(((xx+1)/frequency)*PiOver180) * 100);          centerRatio := 1 - (abs(yy-verticaloffset))/verticaloffset;          yy1 := yy1*centerRatio + yy;          yy2 := yy2*centerRatio + yy;          Image1.Canvas.Line(xx, Trunc(yy1), Succ(xx), Trunc(yy2));        end;    end;end; procedure TForm1.FormCreate(Sender: TObject);var  start, elapsed: LongInt;begin  start := DateTimeToTimeStamp(Time).Time;  DrawWave;  elapsed := DateTimeToTimeStamp(Time).Time - start;  Caption := Format('Time to draw sine wave = %d ms', [elapsed]);end; ---------------------------------------------------------------------- QuinnMartin · 2023-02-16 23:14 Thanks, I tried out the code but it doesn't address the speed issue... it runs about the same speed. I do appreciate the suggestions for writing better code. For whatever reason yy isn't initializing properly the way you have it set up... if I set a breakpoint on the very first line, yy is always some random large number.  It seems like the code is ignoring the value that was set.  I tried changing yy to yyy, same problem.  If I hardcode yyy := 0; on the line before the breakpoint, then the value is set properly.  I don't understand why it is ignoring the value being set in the var section. ---------------------------------------------------------------------- howardpc · 2023-02-16 23:34 Perhaps you should show a compilable example of yours which is so slow. Here the sine drawing code I showed times at 0ms, and yy is initialized correctly. ---------------------------------------------------------------------- TRon · 2023-02-16 23:45 @QuinnMartin If you require full horsepower then you (sh/c)ould make use of a pre- calculated sinus table. Although howardpc' results seem to be satisfactory enough. ---------------------------------------------------------------------- QuinnMartin · 2023-02-17 00:00 Thanks for the suggestions... I made some changes and I managed to speed it up significantly.  I did backtrack to my old code (below) and used a double buffer technique, drawing to a non-visible image (ImgBuf) and then transferred it via BitBlt to the visible Image1. Unfortunately I'm not sure why it started running faster as I got distracted trying to troubleshoot why Image1 wasn't drawing (I had to add Image1.refresh for some reason below or nothing would ever appear). The code is enormously faster and I am guessing something was trying to run updates on the visible component Image1 whenever anything was drawn to it.  Now Image1 is just used for display and only receives the transfer from the double buffer image and that's all. This is the faster code: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function TForm1.DrawWave : integer;var  xx, yy, f1, verticaloffset, yy1, yy2, centerratio, r1, r2, r3 : real;  s1 : string;begin  ImgBuf.Canvas.Rectangle(ImgBlank.ClientRect);  r1 := time*24*60*60;  verticaloffset := 200;   f1 := Wave1Frequency.position;  f1 := f1/10;  if (f1 > 0) then    begin      yy := 0;   // was 0      while (yy < 400) do  // was 600        begin          yy := yy+50;          xx := 1;          while (xx < 1200) do            begin              xx := xx+1;              yy1 := (sin((xx/f1)*pi/180) * 100);              yy2 := (sin(((xx+1)/f1)*pi/180) * 100);              centerratio := 1 - (abs(yy-verticaloffset))/verticaloffset;              yy1 := yy1*centerratio;              yy2 := yy2*centerratio;              yy1 := yy1+yy;              yy2 := yy2+yy;              ImgBuf.Canvas.MoveTo(round(xx),round(yy1));              ImgBuf.Canvas.LineTo(round(xx+1),round(yy2));            end;        end;    end;  Image1.refresh;  // This is needed for some reason before anything will draw  BitBlt(Image1.Canvas.Handle, 0, 0, ImgBuf.Canvas.width, ImgBuf.Canvas.height, ImgBuf.Canvas.Handle, 0, 0, SRCCOPY);  r2 := time*24*60*60;  r3 := (r2-r1)*1000;  str(r3:0:0,s1); Memo1.Lines.Add('Time '+s1+' ms');  result := 1;end; ---------------------------------------------------------------------- speter · 2023-02-17 00:57 QuinnMartin, do you need to draw onto an image? I am attaching a project which uses a simplified version of your code, and draws onto a (non image) canvas _or_ onto a image canvas... The code seems quite fast (though your milage may vary). Note that I made the trackbars have a min value of 1, so you don't need to check f1 > 0. cheers S. ---------------------------------------------------------------------- keeslazarus · 2023-05-06 12:55 I also tried to simplify and speed up the code. The fastest way is to do the drawing in a bitmap and draw the bitmap on the image when finished. Have a look at the code and comments and try it yourself. ---------------------------------------------------------------------- Handoko · 2023-05-06 13:40 @QuinnMartin There should be something wrong with your Lazarus installation, settings or OS/driver/system/hardware. I tested your code with a bit changes but nothing essential, on my Lazarus 2.3.0 GTK2 Linux 64-bit with its default compilation settings on Intel G2020 2 cores processor. It wasn't too slow as you can see on the screenshot. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  xx, yy, f1, verticaloffset, yy1, yy2, centerratio, r1, r2, r3 : real;  s1 : string;begin  Image1.c anvas.rectangle(0,0,Image1.Canvas.Width,Image1.Canvas.Height);   r1 := time*24*60*60;   verticaloffset := 300;   Memo1.Clear;  Image1.Canvas.Pen.Color := clYellow;   f1 := 3; //Wave1Frequency.position;  f1 := f1/10;  if (f1 > 0) then    begin    yy := 300;   // was 0      while (yy < 301) do  // was 600        begin          yy := yy+10;          xx := 1;          while (xx < 1200) do            begin              xx := xx+1;              yy1 := (sin((xx/f1)*pi/180) * 100);              yy2 := (sin(((xx+1)/f1)*pi/180) * 100);              centerratio := 1 - (abs(yy-verticaloffset))/verticaloffset;              yy1 := yy1*centerratio;              yy2 := yy2*centerratio;              yy1 := yy1+yy;              yy2 := yy2+yy;              Image1.Canvas.MoveTo(round(xx),round(yy1));              Image1.Canvas.LineTo(round(xx+1),round(yy2));            end;        end;    end;  r2 := time*24*60*60;  r3 := (r2-r1)*1000;  str(r3:0:0,s1);   Memo1.Lines.Add('Time '+s1+' ms');end; ---------------------------------------------------------------------- Paolo · 2023-05-06 20:29 For sure it is better having a doublebuffer bitmap. In any case your code seems not well optimised why you have the centerratio undpated inside the second while ? it is constant once you compute yy ? why are you computing two times the value of yy1 ? save the last one for next couple of yy1-yy2. have you noticed that updating xx:=xx+1 and then using xx and xx+1 you are skipping the copule of indexes 1-2 and start at 2-3 ? here the simplified version  (Starting with copule 1-2) Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function TForm1.DrawWave : integer;var xx, yy, f1, verticaloffset, yy1, yy2, centerratio, r1, r2, r3 : real; s1 : string;begin  Image1.canvas.recta ngle(0,0,Image1.Canvas.Width,Image1.Canvas.Height);  Image1.canvas.Pen.Color:=clred;  //necessario per vedere il grafico se no vedo tutto nero  r1 := time*24*60*60;   verticaloffset := 300;   f1 := 3; //Wave1Frequency.position;  f1 := f1/10;  if (f1 > 0) then    begin      yy := 300;   // was 0      while (yy < 301) do  // was 600        begin          yy := yy+10;          centerratio := 100*(1 - (abs(yy-verticaloffset))/verticaloffset);  //<-- here          xx := 1;          yy1 := sin((xx/f1)*pi/180)*CenterRatio+yy;  //<-- first value pre computed outside the main loop          Image1.Canvas.MoveTo(round(xx),round(yy1));          while (xx < 1200) do            begin              xx := xx+1;              yy2 := sin((xx/f1)*pi/180)*centerRatio + yy;              Image1.Canvas.LineTo(round(xx),round(yy2));              yy1:=yy2;                                                      //<--  save latest value for next iteration              Image1.Canvas.MoveTo(round(xx),round(yy1));            end;        end;    end;  r2 := time*24*60*60;  r3 := (r2-r1)*1000;  str(r3:0:0,s1);  Memo1.Lines.Add('Time '+s1+' ms'); end; ---------------------------------------------------------------------- marcov · 2023-05-06 21:25 search for lockwindowupdate() ---------------------------------------------------------------------- 440bx · 2023-05-06 23:54 From the Windows API, I'd use either Polyline or PolyPolyline, that would allow multiple curves to be drawn in a single call which should speed things up considerably compared to LineTo and MoveTo combinations. HTH. ---------------------------------------------------------------------- circular · 2023-05-08 10:37 Indeed. In the loop you compute twice each coordinates. Instead you can put them into an array (you know the size from the start) and then feed it into a Polyline call. ---------------------------------------------------------------------- TRon · 2023-05-08 12:40 In addition to what is already said: if you know the resolution beforehand then you  can even use pixels, and skip using lines. That combined with double buffering and a pre-calculated sine tab is afaik the fastest way to do it.