Lazarus

Free Pascal => Beginners => Topic started by: justnewbie on March 06, 2021, 01:05:51 pm

Title: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 01:05:51 pm
Hi guys,
I'm working on a blur effect code, but it freezes. Why?
Code: Pascal  [Select][+][-]
  1. procedure TForm1.MenuBlurClick(Sender: TObject);
  2. var i,j: word;
  3.     origBitmap: TBitmap;
  4.     resultBitmap: TBitmap;
  5.     rgbColor: longint;
  6.     red, green, blue: byte;
  7.     red1, green1, blue1: byte;
  8.     red2, green2, blue2: byte;
  9.     red3, green3, blue3: byte;
  10.     red4, green4, blue4: byte;
  11.     avgRed, avgGreen, avgBlue: byte;
  12. begin
  13.  
  14.      resultBitmap := TBitmap.Create;
  15.      resultBitmap.Width := Image1.Width;
  16.      resultBitmap.Height := Image1.Height;
  17.  
  18.      origBitmap := TBitmap.Create;
  19.      origBitmap.Assign(Image1.Picture.Graphic);
  20.  
  21.      for i := 1 to Image1.Width - 2 do
  22.        for j := 1 to Image1.Height - 2 do
  23.        begin
  24.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i,j]);
  25.          blue := GetBValue(rgbColor);
  26.          green := GetGValue(rgbColor);
  27.          red := GetRValue(rgbColor);
  28.  
  29.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i - 1, j]);
  30.          blue1 := GetBValue(rgbColor);
  31.          green1 := GetGValue(rgbColor);
  32.          red1 := GetRValue(rgbColor);
  33.  
  34.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i, j - 1]);
  35.          blue2 := GetBValue(rgbColor);
  36.          green2 := GetGValue(rgbColor);
  37.          red2 := GetRValue(rgbColor);
  38.  
  39.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i, j + 1]);
  40.          blue3 := GetBValue(rgbColor);
  41.          green3 := GetGValue(rgbColor);
  42.          red3 := GetRValue(rgbColor);
  43.  
  44.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i + 1, j]);
  45.          blue4 := GetBValue(rgbColor);
  46.          green4 := GetGValue(rgbColor);
  47.          red4 := GetRValue(rgbColor);
  48.  
  49.          avgRed := (red1+red2+red3+red4) div 4;
  50.          avgGreen := (green1+green2+green3+green4) div 4;
  51.          avgBlue := (blue1+blue2+blue3+blue4) div 4;
  52.  
  53.          rgbColor := ((red+avgRed) div 2) + (((green+avgGreen) div 2)*256) + (((blue+avgBlue) div 2)*65536);
  54.  
  55.          resultBitmap.Canvas.Pixels[i, j] := rgbColor;        
  56.        end;
  57.      Image1.Picture.Bitmap := resultBitmap;
  58. end;
Title: Re: What's wrong with my code?
Post by: Thaddy on March 06, 2021, 01:27:14 pm
real programmers count from zero, not one.
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 01:33:37 pm
real programmers count from zero, not one.
Thanks.
1./ I am not a real programmer.
2./ There is i-1 and j-1 in the cycle, real programmers should notice it  :)
BTW, any answer to my original question?
Title: Re: What's wrong with my code?
Post by: RayoGlauco on March 06, 2021, 01:36:59 pm
Your code works for me.
Maybe your image is too big, and it takes a very big number of iterations.

Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 01:38:27 pm
Your code works for me.
Maybe your image is too big, and it takes a very big number of iterations.
Strange.
I tried with small image too with no success.
Title: Re: What's wrong with my code?
Post by: wp on March 06, 2021, 01:56:14 pm
You are mixing up the size of the Image component and the size of the bitmap - the size of the image may be different from the size of contained bitmap. Therefore, your code may run out of the allocated memory.

Better for the first lines of your code:
Code: Pascal  [Select][+][-]
  1.      resultBitmap := TBitmap.Create;
  2.      resultBitmap.Width := Image1.Picture.Width;  // Image1.Width;
  3.      resultBitmap.Height := Image1.Picture.Height;  //Image1.Height;
  4.  
  5.      origBitmap := TBitmap.Create;
  6.      origBitmap.Assign(Image1.Picture.Graphic);
  7.  
  8.      for i := 1 to origBitmap.Width do // Image1.Width - 2 do   /// why -2?
  9.        for j := 1 to origBitmap.Height do //Image1.Height - 2 do

When you have a "large" image you may not be happy with the speed of this routine. This is because you use the Pixels[] property which has a huge overhead and is very slow. Better to use ScanLine or intermediate FCL images. But this is another topic - solve the current one first.
Title: Re: What's wrong with my code?
Post by: winni on March 06, 2021, 02:10:04 pm
Hi!

Two tips:

a) use BGRAbitmap (from the Online Package Manager) because TBitmap is an "All-OS-Agreement" that is very slow

b) Using TBitmap you can do in the mid of the loop:
   
   
Code: Pascal  [Select][+][-]
  1.  if j = 1 then
  2.      begin
  3.      label1.Caption := 'I='+IntToStr(i)+'  J='+IntToStr(J);
  4.      application.ProcessMessages:
  5.      end;
  6.  
   
With this code you can see if your code is still working but is only slow.

Winni   

PS.: Another advantage of BGRAbitmap:

if you try to get the color of a pixel outside the boundaries your app wont crash like with  TBitmap but just returns a transparent pixel (0,0,0,0).   


Title: Re: What's wrong with my code?
Post by: Handoko on March 06, 2021, 02:25:12 pm
Don't use TCanvas for any serious project.

I ever tried to write a 2D side scrolling space shooting game using Lazarus - TCanvas only, without any third party graphics library. It works, a bit slow but good enough to run on my old Dual Core laptop. That because I heavily optimized the code. You can search the forum for Deep Platformer, a simple game to prove that you can write games using TCanvas only. It works because it used only simple graphics and optimized the code.

TCanvas is okay for showing some dots, lines, circles and texts. It is okay for GUI application but it does not optimize for performance, even you run it on an Intel i9 RTX 2080, it's still slow because it is not optimized to fully use the power your machine can offer. For beginners, TCanvas is easy to learn and work with. But soon you will realize, it is not suitable for any serious projects. Scanline command is faster but still not hardware optimized. Try other graphics libraries, invest some time on it, you will be glad for doing it.

You may interest to know:

Platformer
https://forum.lazarus.freepascal.org/index.php/topic,44908.0.html (https://forum.lazarus.freepascal.org/index.php/topic,44908.0.html)

Graphics libraries available for Lazarus/FPC
https://wiki.freepascal.org/Graphics_libraries (https://wiki.freepascal.org/Graphics_libraries)
Title: Re: What's wrong with my code?
Post by: engkin on March 06, 2021, 03:17:33 pm
Instead of Word, use a type that includes negative numbers like Integer for the loop variables.
Title: Re: What's wrong with my code?
Post by: jamie on March 06, 2021, 04:07:24 pm
Use scan line instead to obtain a pointer to a row of known pixel formats..

It most likely is working but slow on your end..

Also, if you want to use a WORD (2 byte type) , try using SmallInt instead..

Laz does not fully support the windows way of working with Tbitmaps so it becomes a little ringgit get things to work with examples found  abroad.

 If you still have issues I'll ding into my Delphi archives and see if I can get something that works in Laz for you.

Btw, you don't indicate the Target, Laz and compiler version ?
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 04:35:18 pm
Thanks for the answers guys, especially the good advices for optimization.
Finally I found that the code works but extremely slow.
BUT! I wrote this code many years ago with Delphi (Windows) and worked well.
The Delphi version makes the blur like a shot (some milliseconds) on a 300x200 picture.
How is it possible that Lazarus does it in 53 seconds on the same 300x200 picture?!



Title: Re: What's wrong with my code?
Post by: jamie on March 06, 2021, 04:43:27 pm
Lazarus does not handle a Bitmap in the same way and thus suffers the raft of Lard when used that way..

Use the ScanLine to obtain an array of raw memory to the image.

make sure you use the same data type as that of the image... 24, 32 bit etc..
Title: Re: What's wrong with my code?
Post by: wp on March 06, 2021, 05:26:53 pm
The Delphi version makes the blur like a shot (some milliseconds) on a 300x200 picture.
How is it possible that Lazarus does it in 53 seconds on the same 300x200 picture?!
Then your code must be doing something else during the calculation, such as repainting the image after changing the pixels.

My attached demo is based on your code (i.e. accessing the Pixels property) and it does it in less than 0.5 seconds.
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 05:45:06 pm
Then your code must be doing something else during the calculation, such as repainting the image after changing the pixels.

My attached demo is based on your code (i.e. accessing the Pixels property) and it does it in less than 0.5 seconds.
Very strange, your code ran in 37 seconds for me.
There are no any special thing on my form, just some dialogs, see the full code:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, Menus, ExtDlgs,
  9.   ComCtrls, ExtCtrls, LCLIntf;
  10.  
  11.  
  12.  
  13. type
  14.  
  15.   { TForm1 }
  16.  
  17.   TForm1 = class(TForm)
  18.     Image1: TImage;
  19.     MainMenu1: TMainMenu;
  20.     MenuFile: TMenuItem;
  21.     MenuAdjustments: TMenuItem;
  22.     MenuExit: TMenuItem;
  23.     MenuAbout: TMenuItem;
  24.     N1: TMenuItem;
  25.     MenuSave: TMenuItem;
  26.     MenuNew: TMenuItem;
  27.     MenuOpen: TMenuItem;
  28.     MenuBlur: TMenuItem;
  29.     MenuEffects: TMenuItem;
  30.     MenuDrawing: TMenuItem;
  31.     OpenPictureDialog1: TOpenPictureDialog;
  32.     ProgressBar1: TProgressBar;
  33.     SavePictureDialog1: TSavePictureDialog;
  34.     ToolBar1: TToolBar;
  35.     procedure MenuBlurClick(Sender: TObject);
  36.     procedure MenuExitClick(Sender: TObject);
  37.     procedure MenuOpenClick(Sender: TObject);
  38.   private
  39.  
  40.   public
  41.  
  42.   end;
  43.  
  44. var
  45.   Form1: TForm1;
  46.  
  47. implementation
  48.  
  49. {$R *.lfm}
  50.  
  51. { TForm1 }
  52.  
  53.  
  54.  
  55. procedure TForm1.MenuOpenClick(Sender: TObject);
  56. begin
  57.   if OpenPictureDialog1.Execute then
  58.   begin
  59.     Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
  60.   end;
  61. end;
  62.  
  63. procedure TForm1.MenuExitClick(Sender: TObject);
  64. begin
  65.   Close;
  66. end;
  67.  
  68. procedure TForm1.MenuBlurClick(Sender: TObject);
  69. var i,j: word;
  70.     origBitmap: TBitmap;
  71.     resultBitmap: TBitmap;
  72.     rgbColor: longint;
  73.     red, green, blue: byte;
  74.     red1, green1, blue1: byte;
  75.     red2, green2, blue2: byte;
  76.     red3, green3, blue3: byte;
  77.     red4, green4, blue4: byte;
  78.     avgRed, avgGreen, avgBlue: byte;
  79. begin
  80.  
  81.      resultBitmap := TBitmap.Create;
  82.      resultBitmap.Width := Image1.Picture.Width;
  83.      resultBitmap.Height := Image1.Picture.Height;
  84.  
  85.      origBitmap := TBitmap.Create;
  86.      origBitmap.Assign(Image1.Picture.Graphic);
  87.  
  88.      ProgressBar1.Min:=1;
  89.      ProgressBar1.Max:=Image1.Picture.Width-2;
  90.  
  91.      for i := 1 to Image1.Picture.Width-2 do
  92.        for j := 1 to Image1.Picture.Height-2 do
  93.        begin
  94.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i,j]);
  95.          blue := GetBValue(rgbColor);
  96.          green := GetGValue(rgbColor);
  97.          red := GetRValue(rgbColor);
  98.  
  99.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i - 1, j]);
  100.          blue1 := GetBValue(rgbColor);
  101.          green1 := GetGValue(rgbColor);
  102.          red1 := GetRValue(rgbColor);
  103.  
  104.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i, j - 1]);
  105.          blue2 := GetBValue(rgbColor);
  106.          green2 := GetGValue(rgbColor);
  107.          red2 := GetRValue(rgbColor);
  108.  
  109.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i, j + 1]);
  110.          blue3 := GetBValue(rgbColor);
  111.          green3 := GetGValue(rgbColor);
  112.          red3 := GetRValue(rgbColor);
  113.  
  114.          rgbColor := ColorToRGB(origBitmap.Canvas.Pixels[i + 1, j]);
  115.          blue4 := GetBValue(rgbColor);
  116.          green4 := GetGValue(rgbColor);
  117.          red4 := GetRValue(rgbColor);
  118.  
  119.          avgRed := (red1+red2+red3+red4) div 4;
  120.          avgGreen := (green1+green2+green3+green4) div 4;
  121.          avgBlue := (blue1+blue2+blue3+blue4) div 4;
  122.  
  123.          rgbColor := ((red+avgRed) div 2) + (((green+avgGreen) div 2)*256) + (((blue+avgBlue) div 2)*65536);
  124.  
  125.          resultBitmap.Canvas.Pixels[i, j] := rgbColor;
  126.          ProgressBar1.Position:=i;
  127.          Application.ProcessMessages;
  128.        end;
  129.      Image1.Picture.Bitmap := resultBitmap;
  130. end;
  131.  
  132. end.
Title: Re: What's wrong with my code?
Post by: Handoko on March 06, 2021, 06:28:35 pm
My guess is you made the Application.ProcessMessages too responsive.

For example if you call it in X-Y loops of 300 x 300 counts then it will be called 90 thousands times. If each call takes about 1 milliseconds, then try to do the calculation then you will know it wastes a lot of time. You should reduce it's responsiveness.

If that really is the case, there are many things you can do. This is one of the tricks:
https://forum.lazarus.freepascal.org/index.php/topic,43806.msg307101.html#msg307101 (https://forum.lazarus.freepascal.org/index.php/topic,43806.msg307101.html#msg307101)
Title: Re: What's wrong with my code?
Post by: wp on March 06, 2021, 06:30:12 pm
Did you run EXACTLY my project, or did you insert the Progressbar and Application.ProcessMessages, too? When I do so the calculation time increases 10fold.
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 06:31:44 pm
My guess is you make Application.ProcessMessages too responsive.

If you call it on an X-Y loop of 300 x 300 then it will be called 90 thousands times. You should reduce it's responsiveness.
I put the ProgressBar in afterwards, so it has nothing to do with the horrible time-interval.
With or without it, speed is extreme slow.
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 06:34:42 pm
Did you run EXACTLY my project, or did you insert the Progressbar and Application.ProcessMessages, too? When I do so the calculation time increases 10fold.
To be honest, I tried to run your project, but couldn't. (I opened it as a project, run, but nothing happened. Thre was no form at all.)
So, I take your Button1Click procedure and put into my code.
Title: Re: What's wrong with my code?
Post by: Handoko on March 06, 2021, 06:39:23 pm
I opened it as a project, run, but nothing happened. Thre was no form at all.

If you opened the project but saw nothing, you should read this:
https://forum.lazarus.freepascal.org/index.php/topic,53535.msg396098.html#msg396098 (https://forum.lazarus.freepascal.org/index.php/topic,53535.msg396098.html#msg396098)

... speed is extreme slow.

Maybe nothing wrong with your code. Can it be virus, antivirus, OS, hardware, etc issue? Can you try to run the code on different machine?
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 06:44:53 pm
Maybe nothing wrong with your code. Can it be virus, antivirus, OS, hardware, etc issue? Can you try to run the code on different machine?
I don't think so. Linux + powerful hardware.
Title: Re: What's wrong with my code?
Post by: Handoko on March 06, 2021, 06:46:25 pm
Can you provide the compile-able source code, in a zip format? I use Linux too. I'm interested to test it myself.
Title: Re: What's wrong with my code?
Post by: wp on March 06, 2021, 06:50:45 pm
Did you run EXACTLY my project, or did you insert the Progressbar and Application.ProcessMessages, too? When I do so the calculation time increases 10fold.
To be honest, I tried to run your project, but couldn't. (I opened it as a project, run, but nothing happened. Thre was no form at all.)
So, I take your Button1Click procedure and put into my code.
Sorry, I probably forgot to set the compatibility flag (I am on Laz trunk). This one should be correct for you:
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 06:57:44 pm
Can you provide the compile-able source code, in a zip format? I use Linux too. I'm interested to test it myself.
Here you are, thanks Handoko.
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 07:04:41 pm
@wp:
Now it worked, time is horrible, see attached.
Title: Re: What's wrong with my code?
Post by: Handoko on March 06, 2021, 07:15:50 pm
Your MyProject needs 21 seconds to process a 450 x 300 pixels image on my Ubuntu Mate G2020 2-cores computer.

Now inspecting to see if there is anything can be done to improved the performance.
Title: Re: What's wrong with my code?
Post by: wp on March 06, 2021, 07:20:06 pm
OK. Here is a version which avoids the Pixels property. It is still based on the units which come with Lazarus/FPC, in this case IntfGraphics. Unlike using ScanLine it does not require knowledge of the color structure of the pixels. In my tests the previous demo runs in a few milliseconds:
Title: Re: What's wrong with my code?
Post by: wp on March 06, 2021, 07:24:21 pm
I see from your demo that you are planning to write a variety of image processing routines. Then you really should move to an optimized library such as BGRABitmap or Graphics32.
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 07:25:07 pm
OK. Here is a version which avoids the Pixels property. It is still based on the units which come with Lazarus/FPC, in this case IntfGraphics. Unlike using ScanLine it does not require knowledge of the color structure of the pixels. In my tests the previous demo runs in a few milliseconds:
Got this error:
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 07:28:54 pm
I see from your demo that you are planning to write a variety of image processing routines. Then you really should move to an optimized library such as BGRABitmap or Graphics32.
It's just a little playing, nothing serious.
I no need for great optimization, just wanted to try some pixel modifications.
But, around 40 seconds it is a bit unplayable. It should be much faster, but cannot see the reason of this horrible speed.
As I said, exactly the same code ran with Delphi within milliseconds.
Title: Re: What's wrong with my code?
Post by: Handoko on March 06, 2021, 07:38:16 pm
Accessing pixel information using Pixel[x,y] command is low, using ScanLine is much faster.

But why Delphi faster? I think for cross platform reason it is not properly optimized. You can try Graphics32. I heard it is several times faster than traditional canvas and it supports both Delphi and Lazarus.

Quote
Fast per-pixel access up to 100 times faster compared to standard TBitmap
Source: https://en.wikipedia.org/wiki/Graphics32

https://github.com/graphics32/
Title: Re: What's wrong with my code?
Post by: wp on March 06, 2021, 07:43:25 pm
OK. Here is a version which avoids the Pixels property. It is still based on the units which come with Lazarus/FPC, in this case IntfGraphics. Unlike using ScanLine it does not require knowledge of the color structure of the pixels. In my tests the previous demo runs in a few milliseconds:
Got this error:
Strange, for me it works (what is your Lazarus and FPC version?)

Declare them as HBITMAP, instead of THandle. But you must add LCLType to the uses clause.

Code: Pascal  [Select][+][-]
  1. uses
  2.   LCLType,     // <------------ ADD
  3.   IntfGraphics, fpimage;
  4.  
  5. procedure Blur(ASource, ADest: TCustomBitmap);
  6. var
  7.   srcImg, destImg: TLazIntfImage;
  8.   c, c1, c2, c3, c4, avgC, resC: TFPColor;
  9.   hBmp, hMask: HBITMAP;   // <----------- HBITMAP instead of THandle
  10.   i, j: Integer;
  11. begin
  12.   srcImg := ASource.CreateIntfImage;
  13.   destImg := ASource.CreateIntfImage;
  14.   ....
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 08:54:34 pm
Wow!  :o
(My new task: trying to interpret your code.  :))
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 09:22:08 pm
@wp, let me a question:
why only the c1 has type-casting and for the others not?
Code: Pascal  [Select][+][-]
  1. avgC.Red := (Int64(c1.Red) + c2.Red + c3.Red + c4.Red) div 4;
Title: Re: What's wrong with my code?
Post by: wp on March 06, 2021, 09:55:52 pm
TFPColor (avgC, c1, c2, c3, c4) is a record consisting for unsigned 16-bit elements for Red, Green, Blue and Alpha. In the code snippet that you cite, four word values are added for calculating the average Red component. The addition may overflow the range of the word data type. Therefore, I am casting to the next larger integer type (sorry, Int64 is not needed, Int32 or DWord, would be enough). I am casting only the first value because then the sum will be the casted type too, and the same with the other values. The final division by 4 brings the average value back into the word range again.
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 10:09:28 pm
... I am casting only the first value because then the sum will be the casted type too, and the same with the other values...
I didn't know it, thank you!
(Your code is fantastic. An 5120x2880 image is blurred within 3.5 seconds.)
Title: Re: What's wrong with my code?
Post by: wp on March 06, 2021, 10:17:30 pm
(My new task: trying to interpret your code.  :))
I understand. There is not good documentation of the basic fcl-image routines and lazarus IntfGraphics, and I must say that this topic still has many surprises for myself. In https://wiki.freepascal.org/Developing_with_Graphics#Working_with_TLazIntfImage.2C_TRawImage_and_TLazCanvas, you can find some more examples.
Title: Re: What's wrong with my code?
Post by: justnewbie on March 06, 2021, 10:34:58 pm
(My new task: trying to interpret your code.  :))
I understand. There is not good documentation of the basic fcl-image routines and lazarus IntfGraphics, and I must say that this topic still has many surprises for myself. In https://wiki.freepascal.org/Developing_with_Graphics#Working_with_TLazIntfImage.2C_TRawImage_and_TLazCanvas, you can find some more examples.
Thank you very much!
TinyPortal © 2005-2018