Fastest way to moving a part of png image lazarus / 39-Graphics ---------------------------------------------------------------------- lbalas · 2014-02-25 19:34 Hi Everybody, I would like to move a part of png image on canvas and I am searching the fastest way. I would use it on a ARM Linux machine (Olimex A10, 1GHz CPU Mali 400 GPU). The size of png is 120x1800 px but I show only 120x180 window. My solution is the next: Code: [Select]unit Unit1; {$mode objfpc}{$H+} interface uses   Classes, SysUtils, FileUtil, Forms, Controls,   bgrabitmap, bgrabitmaptypes,   Graphics, Dialogs, ExtCtrls, StdCtrls,DateUtils; CONST      _PCS = 4; type   { TForm1 }   TForm1 = class(TForm)     Button1    : TButton;     procedure Button1Click(Sender: TObject);     procedure FormCreate(Sender: TObject);   private     { private declarations }   public     { public declarations }   end;   TMyPaintBox = class(TPaintBox)     iNr    : TBGRABitmap;     Procedure Painting(Sender: TObject);   private     { private declarations }   public     { public declarations }     sr,dr  : TRect;     constructor Create(parOwner:TComponent;parParent:TWinControl;parX,parY:integer);   end; var   Form1      : TForm1;   pb         : Array[1..8] of TmyPaintBox;   iBg        : TImage; implementation {$R *.lfm} { TMyPaintBox } Constructor TMyPaintBox.Create(parOwner:TComponent;parParent:TWinContr ol;parX,parY:integer); Begin   inherited Create(parOwner);   Parent:=parParent;   SetBounds(parX,parY,120,180);   OnPaint:=@Painting;   iNr:=TBGRABitmap.Create('big_transparent_image_120x1980.png');   //I show only a part of the image (w:120xh:180)   sr.top:=0;   sr.Left:=0;   sr.bottom:=180;   sr.right:=120; end; Procedure TMyPaintBox.Painting(Sender: TObject); Begin   iNr.DrawPart(sr,Canvas,0,0,false); end; { TForm1 } procedure TForm1.FormCreate(Sender: TObject); Var   i       : integer; begin   SetBounds(0,0,1366,768);   iBg:= TImage.Create(self);   iBg.Parent:=Self;   iBg:= TBGRABitmap.Create('static_background_behind_the_PNG_image.png');   iBg.SetBounds(0,0,1366,768);   For i:=1 to 6 do       pb[i]:=TMyPaintBox.Create(self,self,-20+i*120,192); //create paintboxes next to each other end; procedure TForm1.Button1Click(Sender: TObject); Var    speed   : integer;    yy,i      : integer; begin   Speed:=2;  //Speed of animation   yy:=0;   Repeat //moving the png image in paintbox        yy:=yy+speed;        For i:=1 to _PCS do        Begin            pb[i].sr.Top:=yy;            pb[i].sr.Bottom:=yy+180;            pb[i].Repaint;        End;        Invalidate;        //Application.ProcessMessages; //I need to call this on Linux, without this the screen doesn't draw the new image        //Sleep(5); //I mark it on slowler ARM machine   Until yy>=1800; //height of png image end; end.                  The problem: this solution moves haltingly the png images (especially if it moves more than 4 images in same time). this code is pretty fast on my notebook but not on ARM Linux machine. Please give me some suggestion how I can improve the speed of png images. Thanks in advance lbalas ---------------------------------------------------------------------- User137 · 2014-02-25 21:07 Something that comes to mind is instead of using multiple paintboxes, you could combine them into 1. If all of them are always updated/painted at the same time, you would gain some performance in it. And what is this? Code: [Select]  iBg:= TImage.Create(self);   iBg.Parent:=Self;   iBg:= TBGRABitmap.Create('static_background_behind_the_PNG_image.png');You create TImage and then overwrite its reference pointer with TBGRABitmap. Would this be how it should be defined aswell? Code: [Select]  iBg        : TBGRABitmap; Sleep(5); is very inaccurate way to delay time. Different operating systems and hardware combinations have different opinions on how long it actually takes, and it will propably sleep different length of time every time. You could measure the time it takes between each move, and sleep if there is still time left until next tick, and only sleep for as long as there is time left. ---------------------------------------------------------------------- lbalas · 2014-02-25 22:02 Hi, thank you for your very fast answer. I will try your proposal tomorrow. Thank you. about iBg pointer: I tried the background image with TImage and TBGRABitmap as well but the TBGRABitmap was too slow so I changed it to TImage. This is an example what I edited in this editor and I forget to modify this row, sorry. I tried this test program on ARM machine without Sleep(5); but the maximum FPS was 18. Before that I used TTimer but I would like to measure the speed of machine with this test prg. Is it drawing faster if I use TBGRACanvas or TBGRACanvas2d instead of Paintbox? Thanks in advance lbalas ---------------------------------------------------------------------- circular · 2014-02-27 18:45 TBGRACanvas and TBGRACanvas2d are drawing techniques, they are not themselves real canvas. If you have many sprites, the recommended way is to use a virtual screen, i.e. to use a TBGRABitmap for the whole screen where you put the background and then the images, and finally you draw the virtualscreen on the canvas in one single PaintBox. You might consider using the component TBGRAVirtualscreen of BGRAControls, that creates and draws the virtualscreen for you while avoiding flickering. See for example: http://www.digeotek.com/analog-clock-created-using-lazarus-and- bgrabitmap ---------------------------------------------------------------------- lbalas · 2014-03-02 00:15 Hi Circular, thank you for your answer. I tried your suggestion, it is working. But when I want to redraw the BGRAVirtualScreen every 30 millisec, the moving particle is jumping. (not flickering just sometimes jumping, if the step would be more than the predetermined value) Please take a look at my test source code. Code: [Select]  TForm1 = class(TForm)     VS: TBGRAVirtualScreen;     Timer1: TTimer;     procedure FormCreate(Sender: TObject);     procedure Timer1Timer(Sender: TObject);     procedure VSRedraw(Sender: TObject; Bitmap: TBGRABitmap);     Procedure MoveImage;   private     { private declarations }   public     { public declarations }     img,imgBackground,imgMovingPart     : TBGRABitmap;   end; var   Form1       : TForm1;   sr        : TRect;   yy          : integer;   step        : integer; implementation {$R *.lfm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject); begin   Step:=5;   yy:=0;   SetBounds(0,0,1280,720);   VS.SetBounds(0,0,1280,720);   sr.Top:=0;   sr.Left:=0;   sr.Right:=120;   sr.Bottom:=180;   CreateImages;   Timer1.Enabled:=True; end; procedure TForm1.Timer1Timer(Sender: TObject); begin      MoveImage;      VS.RedrawBitmap; end; procedure TForm1.CreateImages; Begin   imgBackground:=TBGRABitmap.Create('1280x720_background_image.png');   imgMovingPart:=TBGRABitmap.Create('1800x120_moving_image.png');   img:=TBGRABitmap.Create(1280,720); end; Procedure TForm1.MoveImage; Begin   yy:=yy+step;   sr.Top:=yy;   sr.Bottom:=yy+180;   img.Assign(imgBackground);   //show a part of imgMovingParts like a moving object in a window:   img.PutImagePart(100,192,imgMovingPart,sr,dmDrawWithTransparency,255); end; procedure TForm1.VSRedraw(Sender: TObject; Bitmap: TBGRABitmap); begin   Bitmap.BlendImage(0, 0, img, boLinearBlend); end; Thanks in advance your help! lbalas ---------------------------------------------------------------------- User137 · 2014-03-02 02:07 Try with these (just a guess, that Bottom and Right must be where the corners pixel coordinate is): Code: [Select]  sr.Right:=119;   sr.Bottom:=179; ...   sr.Bottom:=yy+179; ---------------------------------------------------------------------- lbalas · 2014-03-02 14:13 Hi, thanks your answer. I think I phrased my qustion wrong. I would like to move the part of png image every 30 ms (33 FPS) but it seems it doesn't move smoothly. I measured the required time for drawing (with GetTickCount) and my experience is from time to time the 30 ms is not enough to draw the whole image. (usually every 10-15 frame needs more time for drawing than 30 msec.) I tested it on Win7 32bit, WinXP 32 bit and Linux ARM but I got the same result. I think I made a mistake in my code because I think a lot of programmers use this gorgeous unit (BGRA) for move particles. thanks in advance for your attention lbalas ---------------------------------------------------------------------- User137 · 2014-03-02 23:21 Can you try replace this Code: [Select]Bitmap.BlendImage(0, 0, img, boLinearBlend);With something else? Code: [Select]Maybe Bitmap.Draw(0, 0, img);I don't know if you have that command, but i assume the purpose here is only doublebuffering, so blending effect for whole screen, every 30ms is a bit much for software rendering. Or see if instead of Linear, there was Nearest mode possible for that blend parameter. ---------------------------------------------------------------------- lbalas · 2014-03-04 10:33 Hi, I tried to replace BlendImage row to PutImage Code: [Select]  Bitmap.PutImage(yy,0,img,dmFastBlend,255)but it still jumps. Plase Let me show you a simple code what moving a 60x60 png image in a TVirtualScreen and a TPaintBox The size of virtualscreen and paintbox 1280x60, so it is not too big to redraw every 30 ms. The image is only 60x60 pixcels. The problem is the both solution are "jumping" while the image is moving. Code: [Select]unit Unit1; {$mode objfpc}{$H+} interface uses   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,   StdCtrls, Spin, BGRAVirtualScreen, BGRABitmap, BCTypes, BGRABitmapTypes; type   { TForm1 }   TForm1 = class(TForm)     pb: TPaintBox;     Timer1: TTimer;     vs: TBGRAVirtualScreen;     procedure FormCreate(Sender: TObject);     procedure pbPaint(Sender: TObject);     procedure Timer1Timer(Sender: TObject);     procedure vsRedraw(Sender: TObject; Bitmap: TBGRABitmap);   private     { private declarations }     iMovingImg             : TBGRABitmap;   public     { public declarations }   end; var   Form1      : TForm1;   yy         : integer;   step       : integer; implementation {$R *.lfm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject); begin   SetBounds(0,0,1280,200);   vs.SetBounds(0,0,1280,60);   pb.SetBounds(0,100,1280,60);   iMovingImg:=TBGRABitmap.Create('60x60pixels.png');   yy:=0;   step:=5; //Speed of movement   Timer1.Interval:=33; //30 FPS   Timer1.Enabled:=True; end; procedure TForm1.pbPaint(Sender: TObject); begin   iMovingImg.Draw(TPaintBox(Sender).Canvas,yy,0,false); end; procedure TForm1.Timer1Timer(Sender: TObject); begin   yy:=yy+step;   If (yy>=1200) or (yy<=0) Then      step:=-1*step;   //redraw the VirtualScreen:   vs.RedrawBitmap;   //Redraw the Paintbox:   pb.Repaint; end; procedure TForm1.vsRedraw(Sender: TObject; Bitmap: TBGRABitmap); begin   Bitmap.PutImage(yy,0,iMovingImg,dmFastBlend,255); end; end. I put 2 solution to compare the performance of this, and my experience is the VirtualScreen a bit slower than Tpaintbox. I don't know the TTimer is good for timing the movement or not. Is it possible to create smoothly moving image wwith TBGRA package? I don't want to beleive nobody wanted to make moving particle with this excellent package. Please help me to improve the movement of image. I just want to create a smoothly moving image on screen, but I can not even do it with this very small image. Thank you in advance lbalas ---------------------------------------------------------------------- User137 · 2014-03-04 15:52 I'll just leave my test project like this, i didn't use BGRA but if you did, it might be an improvement over this, i just don't know. But the code is showing alternative to TTimer, which is used here when Timer.Enabled = false. TApplicationProperties component is on "Additional" tab of the component palette, and you have to set the onIdle event. Overall it's still jumping a little, but it is propably to be expected of image this size. You would improve the performance a very big amount by using OpenGL. Code: [Select]unit Unit1; {$mode objfpc}{$H+} interface uses   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,   LCLIntf; type   { TForm1 }   TForm1 = class(TForm)     ApplicationProperties1: TApplicationProperties;     pb: TPaintBox;     Timer1: TTimer;     procedure ApplicationProperties1Idle(Sender: TObject; var Done: Boolean);     procedure FormCreate(Sender: TObject);     procedure pbPaint(Sender: TObject);     procedure Timer1Timer(Sender: TObject);   private     iMovingImg, vs: TBitmap;     interval, t, nextTick: cardinal;   public   end; var   Form1: TForm1;   yy         : integer;   step       : integer; implementation {$R *.lfm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject); begin   SetBounds(0,0,1280,200);   vs:=TBitmap.Create;   vs.SetSize(1280,60);   pb.setBounds(0,100,1280,60);   iMovingImg:=TBitmap.Create;   iMovingImg.LoadFromFile('60x60pixels.bmp');   yy:=0;   timer1.Interval:=30; //33 FPS (1000/interval)   step:=round(timer1.Interval/6); //Speed of movement   t:=GetTickCount;   nextTick:=t;   //Timer1.Enabled:=true;   Timer1.Enabled:=false; end; procedure TForm1.ApplicationProperties1Idle(Sender: TObject; var Done: Boolean); var diff: cardinal; begin   if timer1.Enabled then exit;   t:=GetTickCount;   if t>nextTick then begin     nextTick:=nextTick+timer1.interval;     Timer1Timer(nil);   end else begin     diff:=nextTick-t;     if diff>1 then sleep(1);     //if diff>2 then sleep(1); // Possible option   end;   Done:=false; end; procedure TForm1.pbPaint(Sender: TObject); begin   TPaintBox(sender).Canvas.Draw(0, 0, vs); end; procedure TForm1.Timer1Timer(Sender: TObject); begin   yy:=yy+step;   If (yy>=1200) or (yy<=0) Then     step:=-1*step;   //redraw the VirtualScreen:   vs.Canvas.Clear;   vs.Canvas.Draw(yy,0,iMovingImg);   //Redraw Paintbox:   pb.Repaint; end; end. ---------------------------------------------------------------------- lbalas · 2014-03-05 21:45 Thank you for your answer. I tried your timing method. Like you said, the flickering didn't disappear. I use Linux so the TBitmap is not good for me because it is not interlaced. I tried some OpenGL library (ZenGL, GLScene) but these don't support the ARM-Linux. If you know some OpenGl library that is able to achieve the above task please let me know. Thanks everything again, lbalas ---------------------------------------------------------------------- User137 · 2014-03-06 01:55 If you have tried all of those, there is still my nxPascal left  https://code.google.com/p/nxpascal/ I don't know if it compiles, but it relies on dglOpenGL and lazOpenGLContext. If they don't support ARM, then i don't know what will. ---------------------------------------------------------------------- lbalas · 2014-03-07 22:12 I've tried your nxPascal library. It is very nice and I could compile on arm-linux  It is very slow because the CPU does everything. I think your package uses OpenGL. The arm machine supports only OpenGL ES. In your opinion can I convert somehow your units to use the OpenGL ES? Or maybe you have done it yet.  I've found this post: http://bugs.freepascal.org/view.php?id=15632 I don't know the fpc supports the opengl es or not: http://bugs.freepascal.org/view.php?id=24149 Can you confirm it? Can you help me?  Thanks in advance! lbalas ---------------------------------------------------------------------- User137 · 2014-03-08 06:17 I am not entirely sure. Normal OpenGL rendering uses glBegin..glEnd, displaylists and stuff which aren't compatible with OpenGL ES, but OpenGL 4 (and 3?) way of rendering is same and compatible with OpenGL ES. That is what my TGLRenderer class does, use shaders to render everything. It includes also the basic 2D commands, like drawing part of a image which i call patterns. If i remember right, there is only 1 demo about this shader at the moment; Walkers. But what i don't know, is if this requires some special window creation. That depends much on Lazarus, but i believe the header part of dglOpenGL supports it. Also, with the new OpenGL rendering methods, you can no longer use glTranslate, glScale, etc to move the view. For that there is TCamera class in nxPascal, but the demo should be covering that part. .. Oh, but some internal methods relating to setting window may still be using the old functions    And i might add it's not something simple to just go and replace. It would not be backwards compatible for when using old style again. ---------------------------------------------------------------------- circular · 2014-03-14 12:14 Quote from: lbalas on March 02, 2014, 01:15:52 amHi Circular, thank you for your answer. I tried your suggestion, it is working. But when I want to redraw the BGRAVirtualScreen every 30 millisec, the moving particle is jumping. (not flickering just sometimes jumping, if the step would be more than the predetermined value) Please take a look at my test source code. Code: [Select]  TForm1 = class(TForm)     VS: TBGRAVirtualScreen;     Timer1: TTimer;     procedure FormCreate(Sender: TObject);     procedure Timer1Timer(Sender: TObject);     procedure VSRedraw(Sender: TObject; Bitmap: TBGRABitmap);     Procedure MoveImage;   private     { private declarations }   public     { public declarations }     img,imgBackground,imgMovingPart     : TBGRABitmap;   end; var   Form1       : TForm1;   sr        : TRect;   yy          : integer;   step        : integer; implementation {$R *.lfm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject); begin   Step:=5;   yy:=0;   SetBounds(0,0,1280,720);   VS.SetBounds(0,0,1280,720);   sr.Top:=0;   sr.Left:=0;   sr.Right:=120;   sr.Bottom:=180;   CreateImages;   Timer1.Enabled:=True; end; procedure TForm1.Timer1Timer(Sender: TObject); begin      MoveImage;      VS.RedrawBitmap; end; procedure TForm1.CreateImages; Begin   imgBackground:=TBGRABitmap.Create('1280x720_background_image.png');   imgMovingPart:=TBGRABitmap.Create('1800x120_moving_image.png');   img:=TBGRABitmap.Create(1280,720); end; Procedure TForm1.MoveImage; Begin   yy:=yy+step;   sr.Top:=yy;   sr.Bottom:=yy+180;   img.Assign(imgBackground);   //show a part of imgMovingParts like a moving object in a window:   img.PutImagePart(100,192,imgMovingPart,sr,dmDrawWithTransparency,255); end; procedure TForm1.VSRedraw(Sender: TObject; Bitmap: TBGRABitmap); begin   Bitmap.BlendImage(0, 0, img, boLinearBlend); end; Thanks in advance your help! lbalas Here is a way to improve the speed. Basically you can draw directly on the bitmap of the virtual screen: Code: [Select]Procedure TForm1.MoveImage; Begin   yy:=yy+step;   sr.Top:=yy;   sr.Bottom:=yy+180; end; procedure TForm1.VSRedraw(Sender: TObject; Bitmap: TBGRABitmap); begin   Bitmap.PutImage(0,0,imgBackground,dmSet);   Bitmap.PutImagePart(100,192,imgMovingPart,sr,dmDrawWithTransparency, 255); end;