//==============================================================================
//== Réalisation d'un effet de radar                                          ==
//==============================================================================
//== Ecrit en Delphi par Eric Boisvert                                        ==
//== 11 Octobre 2007                                                          ==
//==============================================================================
//== Une idée originale de Gary Rosenzweig                                    ==
//== http://director-online.com/buildArticle.php?id=941                       ==
//==                                                                          ==
//==============================================================================
Unit main;

Interface

Uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  jpeg, ExtCtrls, StdCtrls, Buttons, math;

{==============================================================================}
{                                }Type {                                       }
{==============================================================================}
  TForm1 = Class(TForm)
    Image1: TImage;
    ImageAlpha: TImage;
    ImageBackground: TImage;
    ImageObjet: TImage;
    ImageFinal: TImage;
    Timer1: TTimer;
    ImageIcon: TImage;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Procedure FormCreate(Sender: TObject);
    Procedure FormDestroy(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  End;



{==============================================================================}
{                                }Var {                                        }
{==============================================================================}
  Form1: TForm1;
  bmpTemp, bmpBack, bmpObj: TBitmap;
  AngleRadar:integer;
  Rayon:integer;
  Centre:integer;

{==============================================================================}
{                            }Implementation {                                 }
{==============================================================================}


{$R *.DFM}



//==============================================================================
//== Minimal Code for Correct Rotation of bitmap at any Degres                ==
//== Destination bitmap will be resize at correct size                        ==
//== NOTES: It use Advanced Graphic Mode( compatible with NT4 and over only! )==
Procedure mcRotateBMP(Src, Dest: TBitmap; BackGroundColor: TColor; Angle: extended;Resize:boolean);
Var
  tXF: tagXForm;
  AngleRad, SinA, CosA: extended;
  Pt1X, Pt1Y, Pt2X, Pt2Y, Pt3X, Pt3Y: extended;
  PtMinX, PtMaxX, PtMinY, PtMaxY: extended;
  SrcDiag:extended;
Begin
  //== Convertion des angles ° en radian ==
  AngleRad := Angle / 180 * pi;
  //== Calcule de sin et cos ==
  SinA := Sin(AngleRad);
  CosA := Cos(AngleRad);

  Pt1X := -Src.Height * SinA;
  Pt1Y :=  Src.Height * CosA;
  Pt2X :=  Src.Width  * CosA - Src.Height * SinA;
  Pt2Y :=  Src.Height * CosA + Src.Width * SinA;
  Pt3X :=  Src.Width  * CosA;
  Pt3Y :=  Src.Width  * SinA;

  //== Determine les min et Max pour les x et y ==
  PtMinX := min(0, min(Pt1X, min(Pt2X, Pt3X)));
  PtMinY := min(0, min(Pt1Y, min(Pt2Y, Pt3Y)));
  PtMaxX := max(Pt1X, max(Pt2X, Pt3X));
  PtMaxY := max(Pt1Y, max(Pt2Y, Pt3Y));

  //== Calcule de la diagonnal de l'image source ==
  SrcDiag := sqrt(sqr(src.Height) + sqr(src.Width));

  //== Met le bitmap de destination assez grand pour acceuillir l'image tournée ==
  if Resize then
  begin
    dest.Width := ceil(SrcDiag);
    dest.Height := dest.Width;
  end
  else
  begin
    dest.Width:=src.Width;
    dest.Height:=src.Height;
  end;

  //== Efface la destination avec la couleur de fond ==
  dest.Canvas.brush.Color := BackGroundColor;
  dest.Canvas.Pen.Color := BackGroundColor;
  dest.canvas.Rectangle(0, 0, dest.width, dest.Height);

  //== Met le mode Graphique avancé ( compatible NT4 et + ) ==
  SetGraphicsMode(dest.canvas.Handle, GM_ADVANCED);

  //== Prépare la matrice de rotation ==
  tXF.eM11 :=  CosA;
  tXF.eM12 :=  SinA;
  tXF.eM21 := -SinA;
  tXF.eM22 :=  CosA;
  tXF.eDX := -PtMinX;  //== Offset en X pour être au centre de l'image ==
  tXF.eDY := -PtMinY;  //== Offset en Y pour être au centre de l'image ==

  //== Change les coordonnées ==
  SetWorldTransform(dest.Canvas.Handle, tXF);

  //== Ajoute une Matrice de translation pour être tjrs ==
  //== centrée dans le bitmap de destination ==
  tXF.eM11 := 1;
  tXF.eM12 := 0;
  tXF.eM21 := 0;
  tXF.eM22 := 1;
  tXF.eDX := (dest.Width / 2) - ((PtMaxX - PtMinX) / 2) - abs(min(0, PtMaxX) / 2);
  tXF.eDY := (dest.Height / 2) - ((PtMaxY - PtMinY) / 2) - abs(min(0, PtMaxY) / 2);
  ModifyWorldTransform(dest.Canvas.Handle, tXF, MWT_RIGHTMULTIPLY);

  //== Envoit l'image tournée dans le bitmap de destination ==
  dest.Canvas.Draw(0, 0, src);

  //== Remet les coordonnées default ==
  tXF.eM11 :=  1;
  tXF.eM12 :=  0;
  tXF.eM21 :=  0;
  tXF.eM22 :=  1;
  tXF.eDX  :=  0;
  tXF.eDY  :=  0;

  //== Remet les coordonnées normales ==
  SetWorldTransform(dest.Canvas.Handle, tXF);
  //== Remet le DC en mode Compatible ==
  SetGraphicsMode(dest.canvas.Handle, GM_COMPATIBLE);

End;

//==============================================================================
//=== Function from: Sub0 --> www.developpez.com <--
{----------------------------------------------------------------}
{ Remplacement du masque alpha d'une image par une image         }
{----------------------------------------------------------------}
Procedure MakeBlend(src, dst, back, alpha: TBitmap);
Const
  Trsp = 100;
Var
  ps, pd, pb, pa: pByteArray;
  x, y, A: Integer;
Begin
  src.PixelFormat := pf32bit;
  dst.Assign(src);
  back.PixelFormat := pf32bit;
  alpha.PixelFormat := pf32bit;
  For y := 0 To Min(src.Height, back.Height) - 1 Do
  Begin
    ps := src.ScanLine[y];
    pd := dst.ScanLine[y];
    pb := back.scanline[y];

    If (alpha <> Nil) Then
      pa := alpha.ScanLine[y];
    For x := 0 To Min(src.Width, back.Width) - 1 Do
    Begin
      A := Round((pa[x * 4]) * (Trsp / 100));
      If (A = 0) Then
      Begin
        pd[x * 4 + 2] := pb[x * 4 + 2];
        pd[x * 4 + 1] := pb[x * 4 + 1];
        pd[x * 4 + 0] := pb[x * 4 + 0];
      End
      Else If (A < $FF) Then
      Begin
        pd[x * 4 + 2] := Round(Max(Min(
          pb[x * 4 + 2] + A * (ps[x * 4 + 2] - pb[x * 4 + 2]) / $FF, $FF), 0));
        pd[x * 4 + 1] := Round(Max(Min(
          pb[x * 4 + 1] + A * (ps[x * 4 + 1] - pb[x * 4 + 1]) / $FF, $FF), 0));
        pd[x * 4 + 0] := Round(Max(Min(
          pb[x * 4 + 0] + A * (ps[x * 4 + 0] - pb[x * 4 + 0]) / $FF, $FF), 0));
      End;
    End;
  End;
End;



//==============================================================================
//== FormCreate()
Procedure TForm1.FormCreate(Sender: TObject);
Begin
  AngleRadar:=0;
  bmpTemp := Tbitmap.create;
  bmpBack := Tbitmap.create;
  bmpObj := Tbitmap.create;

  bmpTemp.width := Image1.picture.graphic.width;
  bmpTemp.height := Image1.picture.graphic.height;
  bmpBack.width := Image1.picture.graphic.width;
  bmpBack.height := Image1.picture.graphic.height;
  bmpObj.width := Image1.picture.graphic.width;
  bmpObj.height := Image1.picture.graphic.height;

  //== Créer un image background avec seulement un cercle dedans ==
  bmpBack.Canvas.Pen.Color:=rgb(128,128,128);
  bmpBack.Canvas.Pen.Width:=1;
  bmpBack.Canvas.Brush.Color:=rgb(0,0,0);
  bmpBack.Canvas.FloodFill(1, 1, rgb(0, 0, 0), fsBorder);
  bmpBack.Canvas.Ellipse (0,0,200,200);

  //== On l'affiche ici dans un TImage pour des besoins de compréhension ==
  ImageBackground.Canvas.Draw(0, 0, bmpBack);

  //== Dessine quelque chose sur l'image radar ==
  //== ici, ca pourrait-être une mini map      ==

  bmpObj.Canvas.Brush.Color:=rgb(0,0,0);
  bmpObj.Canvas.Pen.Color:=rgb(0,0,0);
  bmpObj.Canvas.FloodFill(1,1,rgb(243,4,4),fsBorder);
  bmpObj.Canvas.Brush.Color:=rgb(0,190,0);
  bmpObj.Canvas.Pen.Color:=rgb(0,255,0);
  bmpObj.Canvas.Ellipse(179, 119, 185, 125);
  bmpObj.Canvas.Ellipse(180, 120, 184, 124);
  bmpObj.Canvas.Ellipse(181, 121, 183, 123);
  bmpObj.Canvas.Ellipse(182, 122, 182, 122);
  bmpObj.Canvas.Draw(150,150,ImageIcon.Picture.Icon);

  bmpObj.canvas.Font.Color:=rgb(0,255,0);
  bmpObj.canvas.brush.Style:=bsClear;
  bmpObj.canvas.Font.Style:=[fsBold] ;
  bmpObj.canvas.TextOut(45,25,'Ici tu text');
  bmpObj.canvas.TextOut(25,150,'Ici un icon copié -->');
  //== On l'affiche ici dans un TImage pour des besoins de compréhension ==
  ImageObjet.Canvas.Draw(0, 0, bmpObj);

  //== Prépare le TImage Final pour la reception du bitmap final ==
  ImageFinal.Picture.Bitmap.Width:= Image1.picture.graphic.width;
  ImageFinal.Picture.Bitmap.Height:= Image1.picture.graphic.Height;

  //== Prépare le bitmap pour dessiner la ligne verte du Radar Final ==
  bmpTemp.Canvas.Pen.Color:=rgb(0,255,0);
  Centre:=bmpTemp.width div 2;
  Rayon:=bmpTemp.width div 2;

  //== Lance le Radar ==
  Timer1.Enabled:=TRUE;
End;

//==============================================================================
//== FormDestroy
Procedure TForm1.FormDestroy(Sender: TObject);
Begin
  //== Arret du Radar ==
  Timer1.Enabled:=FALSE;

  //== Libération des ressources ==
  bmpTemp.free;
  bmpBack.free;
  bmpObj.free;
End;


//==============================================================================
//== Timer du Radar
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  //== Rotation anti-horaire ==
  AngleRadar:=AngleRadar-5;
  if AngleRadar<0 then AngleRadar:=360;

  //== Rotation du masque Radar Alpha ==
  mcRotateBMP( Image1.Picture.Bitmap     //Source
              ,ImageAlpha.Picture.Bitmap //Destination
              ,rgb(0,0,0)                //Background color
              ,AngleRadar                //Rotate at x°
              ,FALSE);                   //Resize destination Non Merci!

  //== Superpose toutes les images ==
  MakeBlend(  bmpObj                     //Source
             ,bmpTemp                    //Destination
             ,bmpBack                    //Background
             ,ImageAlpha.Picture.Bitmap);//Masque Alpha

  //== Ajoute une ligne verte... ==
  bmpTemp.Canvas.MoveTo(Centre,Centre);
  bmpTemp.Canvas.LineTo( Centre+round( sin(AngleRadar / 180 * pi)*Rayon)
                        ,Centre+round(-cos(AngleRadar / 180 * pi)*Rayon) );

  //== Copie l'image finale dans un TImage ==
  ImageFinal.Picture.Bitmap.Canvas.CopyRect(
      ImageFinal.ClientRect,
      bmpTemp.Canvas,
      bmpTemp.Canvas.ClipRect);



end;

End.

