Recent

Author Topic: Transparent animation  (Read 1103 times)

majolika

  • Jr. Member
  • **
  • Posts: 73
Transparent animation
« on: February 07, 2025, 09:29:03 pm »
Hello everyone!
It's me again with my newbie's question.
I've almost finished my first Lazarus "mine sweeper" project, but I'm stuck trying to make a transparent animation.
The idea was like this: there is a TBitmap (FieldBitmap in my code), on which I draw the game field and everything that happens on it, and at the right moment on another transparent TBitmap2 (AnimationBitmap in my code) I draw an animation on a timer, display it on top of the game field and erase what was drawn when animation finished.
As always, I reproduced this in a test project and it did not work.
I faced two problems:

1. I found out that TBitmap cannot be transparent. But then why does it have the transparency property?! I don't understand. Ok, instead of TBitmap2, I tried using TImage, but this also didn't work. I think, I'm doing something wrong.

2. If, when the application is first launched, the first click on the game field falls on the middle mouse button (I assigned it to start the animation for testing), then part of the animation is drawn in the upper left corner of TBitmap2 for some reason. When restarting the game field or if the first click was the right or left mouse button, then partial rendering in the upper left corner doesn't occur. I assume that the problem is in the initialization of CellXCenter and CellYCenter variables that I use when drawing the animation.

How can I deal with both of these problems?
I will be grateful for any hints.

Here is the code of the test project, and in the attached file is the entire project.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, LCLType;
  9.  
  10. type
  11.  
  12.   TForm1 = class(TForm)
  13.     Button1: TButton;
  14.     Button2: TButton;
  15.     Label1: TLabel;
  16.     Label2: TLabel;
  17.     PaintBox1: TPaintBox;
  18.     Timer1: TTimer;
  19.     procedure Button1Click(Sender: TObject);
  20.     procedure Button2Click(Sender: TObject);
  21.     procedure FormCreate(Sender: TObject);
  22.     procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  23.     procedure PaintBox1MouseMove(Sender: TObject;
  24.               Shift: TShiftState; X, Y: Integer);
  25.     procedure PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
  26.               Shift: TShiftState; X, Y: Integer);
  27.     procedure PaintBox1Paint(Sender: TObject);
  28.     procedure Timer1Timer(Sender: TObject);
  29.   private
  30.     FieldBitmap, AnimationBitmap: TBitmap;
  31.     AnimationFrame: Integer;
  32.     procedure FieldInit(Cols, Rows: Integer);
  33.     procedure StartCaptureAnimation;
  34.     procedure StopCaptureAnimation;
  35.     procedure ClearAnimationBitmap;
  36.  
  37.   public
  38.   end;
  39.  
  40. var
  41.   Form1: TForm1;
  42.   BoxSize: Integer = 24;
  43.   FieldRow: integer = 0;
  44.   FieldCol: integer = 0;
  45.   Button1Clicked: Boolean = False;
  46.   Button2Clicked: Boolean = False;
  47.   FieldWidth, FieldHeight: Integer;
  48.   ThereWillBeBlood: Boolean = False;
  49.   CellXCenter, CellYCenter: Integer;
  50.  
  51.  
  52. implementation
  53.  
  54. {$R *.lfm}
  55.  
  56. procedure TForm1.Button1Click(Sender: TObject);
  57. begin
  58.   Button1Clicked := True;
  59.   FieldRow := 5;
  60.   FieldCol := 5;
  61.   FieldInit(FieldCol, FieldRow);
  62. end;
  63.  
  64. procedure TForm1.Button2Click(Sender: TObject);
  65. begin
  66.     Button2Clicked := True;
  67.     FieldRow := 20;
  68.     FieldCol := 20;
  69.     FieldInit(FieldCol, FieldRow);
  70. end;
  71.  
  72. procedure TForm1.FormCreate(Sender: TObject);
  73. begin
  74.   FieldBitmap := TBitmap.Create;
  75.   AnimationBitmap := TBitmap.Create;
  76. end;
  77.  
  78. procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  79. begin
  80.   FieldBitmap.Destroy;
  81.   AnimationBitmap.Destroy;
  82. end;
  83.  
  84. procedure TForm1.PaintBox1MouseMove(Sender: TObject;
  85.           Shift: TShiftState; X, Y: Integer);
  86. var
  87.   CellX, CellY: Integer;
  88. begin
  89.   Label1.Caption := X.ToString + ', ' + Y.ToString;
  90.  
  91.   CellX := X div BoxSize;
  92.   CellY := Y div BoxSize;
  93.   Label2.Caption := CellX.ToString + ', ' + CellY.ToString;
  94. end;
  95.  
  96.  
  97.  
  98. procedure TForm1.PaintBox1Paint(Sender: TObject);
  99. begin
  100.   PaintBox1.Canvas.Draw(0,0, FieldBitmap);
  101.   if ThereWillBeBlood then
  102.      PaintBox1.Canvas.Draw(0, 0, AnimationBitmap);
  103. end;
  104.  
  105.  
  106.  
  107. procedure TForm1.FieldInit(Cols, Rows: Integer);
  108. var
  109.   x, y: Integer;
  110.   r: TRect;
  111. begin
  112.  
  113.   FieldWidth := Cols * BoxSize;
  114.   FieldHeight := Rows * BoxSize;
  115.  
  116.   PaintBox1.Width := FieldWidth;
  117.   PaintBox1.Height := FieldHeight;
  118.  
  119.   FieldBitmap.Clear;
  120.   AnimationBitmap.Clear;
  121.  
  122.   FieldBitmap.SetSize(FieldWidth, FieldHeight);
  123.   FieldBitmap.Canvas.Brush.Color := clNone;
  124.   FieldBitmap.Canvas.FillRect(0, 0, FieldWidth, FieldHeight);
  125.  
  126.   AnimationBitmap.SetSize(FieldWidth, FieldHeight);
  127.   AnimationBitmap.Transparent := True;
  128.   AnimationBitmap.Canvas.Brush.Color := clNone;
  129.   AnimationBitmap.Canvas.FillRect(0, 0, FieldWidth, FieldHeight);
  130.  
  131.   for x := 0 to Cols - 1 do
  132.   begin
  133.     for y := 0 to Rows - 1 do
  134.     begin
  135.       r := rect(x * BoxSize, y * BoxSize, x * BoxSize + BoxSize, y * BoxSize + BoxSize);
  136.       FieldBitmap.Canvas.Frame3d(r, 2, bvRaised);
  137.     end;
  138.   end;
  139.  
  140.   PaintBox1.Refresh;
  141.  
  142. end;
  143.  
  144.  
  145.  
  146. procedure TForm1.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
  147.           Shift: TShiftState; X, Y: Integer);
  148. var
  149.   CellX, CellY: Integer;
  150. begin
  151.  
  152.   CellX := (X div BoxSize) * Boxsize;
  153.   CellY := (Y div BoxSize) * Boxsize;
  154.   CellXCenter := CellX + (Boxsize div 2);
  155.   CellYCenter := CellY + (Boxsize div 2);
  156.  
  157.   case Button of
  158.      mbLeft: begin
  159.        FieldBitmap.Canvas.Brush.Color := RGBToColor(225, 25, 25);
  160.        FieldBitmap.Canvas.Brush.Style := bsSolid;
  161.        FieldBitmap.Canvas.Pen.Color   := RGBToColor(225, 25, 25);
  162.        FieldBitmap.Canvas.Pen.Style   := psSolid;
  163.        FieldBitmap.Canvas.Ellipse(CellX + 2, CellY + 2, CellX + BoxSize - 2, CellY + BoxSize - 2);
  164.      end;
  165.      mbRight: begin
  166.        FieldBitmap.Canvas.Brush.Color := RGBToColor(25, 225, 25);
  167.        FieldBitmap.Canvas.Brush.Style := bsSolid;
  168.        FieldBitmap.Canvas.Pen.Color   := RGBToColor(25, 225, 25);
  169.        FieldBitmap.Canvas.Pen.Style   := psSolid;
  170.        FieldBitmap.Canvas.Ellipse(CellX + 2, CellY + 2, CellX + BoxSize - 2, CellY + BoxSize - 2);
  171.      end;
  172.      mbMiddle: begin
  173.        ThereWillBeBlood := True;
  174.        StartCaptureAnimation;
  175.      end;
  176.   end;
  177.  
  178.   PaintBox1.Repaint;
  179.  
  180. end;
  181.  
  182.  
  183.  
  184. procedure TForm1.StartCaptureAnimation;
  185. begin
  186.   AnimationFrame := 0;
  187.   Timer1.Interval := 50;               // slower
  188.   //Timer1.Interval := 15;             // faster
  189.   Timer1.Enabled := True;
  190. end;
  191.  
  192.  
  193.  
  194. procedure TForm1.ClearAnimationBitmap;
  195. // -----------------------------------
  196. // clearing AnimationBitmap.Canvas
  197. // -----------------------------------
  198. begin
  199.  
  200.   // -- method 1 -- works strange
  201.   //AnimationBitmap.TransparentColor := clFuchsia;
  202.   //AnimationBitmap.Canvas.Brush.Color := clFuchsia;
  203.   //AnimationBitmap.Canvas.FillRect(0, 0, FieldWidth, FieldHeight);
  204.  
  205.   // -- method 2 - works not perfect
  206.   //AnimationBitmap.Clear;
  207.   //AnimationBitmap.SetSize(FieldWidth, FieldHeight);
  208.  
  209.   // -- method 3 - works not perfect
  210.   AnimationBitmap.Canvas.Brush.Color := clNone;
  211.   //AnimationBitmap.Canvas.Brush.Color := clFuchsia; // clFuchsia instead of clNone - for clarity
  212.   AnimationBitmap.Canvas.FillRect(0, 0, FieldWidth, FieldHeight);
  213.  
  214. end;
  215.  
  216.  
  217.  
  218. procedure TForm1.StopCaptureAnimation;
  219. begin
  220.   Timer1.Enabled := False;
  221.  
  222.   ClearAnimationBitmap;
  223.  
  224.   PaintBox1.Invalidate;
  225.   ThereWillBeBlood := False;
  226. end;
  227.  
  228.  
  229.  
  230. procedure TForm1.Timer1Timer(Sender: TObject);
  231. var
  232.   i, x, y, size: Integer;
  233. begin
  234.   AnimationFrame := AnimationFrame + 1;
  235.  
  236.   ClearAnimationBitmap;
  237.  
  238.   AnimationBitmap.Canvas.Brush.Color := RGBToColor(150, 0, 0);
  239.   AnimationBitmap.Canvas.Pen.Style := psClear;
  240.   AnimationBitmap.Canvas.Brush.Style := bsSolid;
  241.   AnimationBitmap.Canvas.Ellipse(CellXCenter - AnimationFrame * 4, CellYCenter - AnimationFrame * 4,
  242.                                  CellXCenter + AnimationFrame * 4, CellYCenter + AnimationFrame * 4);
  243.  
  244.   Randomize;
  245.   for i := 0 to 10 do
  246.   begin
  247.     x := CellXCenter + Random(AnimationFrame * 15) - (AnimationFrame * 7);
  248.     y := CellYCenter + Random(AnimationFrame * 15) - (AnimationFrame * 7);
  249.     size := Random(8) + 3;
  250.     AnimationBitmap.Canvas.Ellipse(x - size, y - size, x + size, y + size);
  251.   end;
  252.  
  253.   PaintBox1.Invalidate;
  254.  
  255.   if AnimationFrame > 10 then
  256.     StopCaptureAnimation;
  257. end;
  258.  
  259. end.
  260.  
Lazarus 3.8 (rev lazarus_3_8) FPC 3.2.2 x86_64-win64-win32/win64

TRon

  • Hero Member
  • *****
  • Posts: 4157
Re: Transparent animation
« Reply #1 on: February 08, 2025, 08:14:31 am »
The logic behind it does not work simply because transparency is not the same as an true alpha channel (it is fake transparency providing the illusion of a pixel being opaque or not). I try come up with an example later but I am a bit busy atm so might take me a while.

If you want to have something to do in the mean-time try have a lok at 32-bit true alpha graphic formats or as an alternative how to "blend" two normal images together into one by means of bitmasking. That is unless someone else is able to come up with a more decent answer on a shorter notice :)
« Last Edit: February 08, 2025, 08:17:45 am by TRon »
Today is tomorrow's yesterday.

majolika

  • Jr. Member
  • **
  • Posts: 73
Re: Transparent animation
« Reply #2 on: February 08, 2025, 08:33:18 am »
I try come up with an example later but I am a bit busy atm so might take me a while.
It would be nice of you! I will patiently wait and while I wiil be waiting I definitly

have a look at 32-bit true alpha graphic formats
Lazarus 3.8 (rev lazarus_3_8) FPC 3.2.2 x86_64-win64-win32/win64

majolika

  • Jr. Member
  • **
  • Posts: 73
Re: Transparent animation
« Reply #3 on: February 08, 2025, 03:05:58 pm »
Allright, I simplified my code to just putting a blue circle on a middle mouse click. No animation for now, just trying to get transparency on the AnimationBitmap.

mbLeft — red circle
mbRight — green circle
mbMiddle — blue circle

In FieldInit trying this code:
Code: Pascal  [Select][+][-]
  1.   //AnimBitmap.PixelFormat := pf24bit;
  2.   AnimBitmap.PixelFormat := pf32bit;
  3.   AnimBitmap.SetSize(FieldWidth, FieldHeight);
  4.   AnimBitmap.TransparentColor := clBlack;
  5.   AnimBitmap.TransparentMode := tmFixed;
  6.   //AnimBitmap.TransparentMode := tmAuto;
  7.   AnimBitmap.Transparent := True;
  8.   AnimBitmap.Canvas.Brush.Color := clBlack;
  9.   AnimBitmap.Canvas.FillRect(0, 0, FieldWidth, FieldHeight);
  10.  

Combination of PixelFormat and TransperentMode gives me two strange behaviors:

pf24bit, tmAuto - black background, multiple blue circles
pf24bit, tmFixed - transparent background, single blue circle in a first click position
pf32bit, tmAuto - black background, multiple blue circles
pf32bit, tmFixed - black background, multiple blue circles

It's so confusing...

P.S.
I was trying to use TBGRABitmap instead of TBitmap. It works fine. Just as I expected (transparent background, multiple blue circles).
I just need to realize how to draw semi-transparent primitives on a fully transparent background.
But what if I don't want to use 3rd party components?

P.P.S.
Reading this article gives me an idea that using TLazIntfImage is a total mess.
You need a TLazIntfImage, TLaxCanvas, TBitmap and Timage (and/or maybe TPaintBox, I don't know) just to get one semi-transparent circle?! Are you kidding me?!
Lazarus 3.8 (rev lazarus_3_8) FPC 3.2.2 x86_64-win64-win32/win64

majolika

  • Jr. Member
  • **
  • Posts: 73
Re: Transparent animation
« Reply #4 on: February 08, 2025, 03:10:14 pm »
I forgot to attach my testing project. Here it is.
Lazarus 3.8 (rev lazarus_3_8) FPC 3.2.2 x86_64-win64-win32/win64

majolika

  • Jr. Member
  • **
  • Posts: 73
Re: Transparent animation
« Reply #5 on: February 08, 2025, 08:17:52 pm »
Ok, now transparent animation works fine with TBGRABitmap (with one exception).
But what if I don't want to use 3rd party components? Can I make the same animation with just LCL?

Now, to the «one exception»: incorrect initialization of CellXCenter/CellYCenter is still here.
If the animation is fast (with a small Timer interval) you can barely see it.
But if the animation is slow... at the top-left corner there is a part of animation appears.

Steps to reproduce:
1. Launch application:
2. Draw the field with any of two buttons;
3. Middle click on the field.

Current phase of the testing project is in the attachment.

P.S.
Got it!
The problem was in Timer handler. When it launches for the first time it doesn't know about Cell(X/Y)Center. But why? It launches _after_ Cell(X/Y)Center initialization. Seems strangr to me.
Anyway, this tow lines in FormCreate do the trick.

Timer1.Enabled := True;
Timer1.Enabled := False;

And now I can incorporate animation in my «mine sweeper project».
« Last Edit: February 08, 2025, 08:46:20 pm by majolika »
Lazarus 3.8 (rev lazarus_3_8) FPC 3.2.2 x86_64-win64-win32/win64

TRon

  • Hero Member
  • *****
  • Posts: 4157
Re: Transparent animation
« Reply #6 on: February 09, 2025, 07:23:48 pm »
Apologies for the delay.

I see that you made quite some progress majolika. Nice !

I have some sort of a solution for when not wanting to use a 3th party implementation but not sure if this is what meets the criteria for your project (please let me know).

It is based on your test17 example.

PS: in the example  you forgot to disable the timer so that was running a complete cycle of animation in the background. Depending on how quick you pressed the middle mouse button ofc.
Today is tomorrow's yesterday.

majolika

  • Jr. Member
  • **
  • Posts: 73
Re: Transparent animation
« Reply #7 on: February 09, 2025, 07:52:31 pm »
you forgot to disable the timer so that was running a complete cycle of animation in the background.
Oh! That's why the parasite drawings happened! Thanks a lot for your attentiveness!
 
I have some sort of a solution for when not wanting to use a 3th party implementation but not sure if this is what meets the criteria for your project (please let me know).
As for now I'm ok with BGRABitmap. Although it's documentation is not so detailed as it could be it has a lots of examples.
But... It would be interesting to see what and how I can do with only LCL features.
My criteria is something in between KISS principle and Zen of Python. :)
Lazarus 3.8 (rev lazarus_3_8) FPC 3.2.2 x86_64-win64-win32/win64

majolika

  • Jr. Member
  • **
  • Posts: 73
Re: Transparent animation
« Reply #8 on: February 09, 2025, 08:22:22 pm »
For now i'm stuck trying to algorithmize waves after a thrown stone.
Alas, such type of algorithmization is not my strong point. :( Blot was simple enough but waves...

P.S.
And I'm not sure how to pause application running while Timer do his job.
It causes an unwanted behavior: my game shows Win/Lose message although blot is still animating.

P.P.S.
My testing point is the attachment.
« Last Edit: February 09, 2025, 08:48:58 pm by majolika »
Lazarus 3.8 (rev lazarus_3_8) FPC 3.2.2 x86_64-win64-win32/win64

 

TinyPortal © 2005-2018