4

Sometimes when we use an installer we need to show an animated GIF so the user knows that the installer is working.

It seems as Inno Setup does not provide a standard way to display animated GIFs. Would it be possible to use Windows built in GDI+ to display the animated GIFs ?

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992

1 Answers1

4

Inno Setup does not provide a straigth way to do it. You will find some workarounds that use several bitmaps and replace them using a timer. But doing some research here and there I was able to implement Windows GDI+ support from Inno Setup!

These are the functions to implement:

function SetTimer( hWnd, nIDEvent, uElapse, lpTimerFunc: Longword ): Longword;
external 'SetTimer@user32.dll stdcall';

function KillTimer( hWnd: HWND; uIDEvent: UINT ): BOOL; 
external 'KillTimer@user32.dll stdcall'; 

function GdiplusStartup( var token: Longword; var inputbuf: GdiPlusStartupInput; var outputbuf: GdiplusStartupOutput ): GpStatus;
external 'GdiplusStartup@GdiPlus.dll stdcall';

function GdipCreateFromHWND( hWnd: HWND; var graphics: GpGraphics ): GpStatus;
external 'GdipCreateFromHWND@GdiPlus.dll stdcall';

function GdipLoadImageFromFile( filename: string; var image: GpImage ): GpStatus;
external 'GdipLoadImageFromFile@GdiPlus.dll stdcall';

function GdipDrawImageRect( graphics: GpGraphics; image: GpImage; x,y: single; width, height: single ): GpStatus;
external 'GdipDrawImageRect@GdiPlus.dll stdcall';

function GdipImageGetFrameDimensionsCount( image: GpImage; var count: Integer ): GpStatus;
external 'GdipImageGetFrameDimensionsCount@GdiPlus.dll stdcall';

function GdipImageGetFrameCount( image: GpImage; var dimensionID: TGuid; var count: Integer ): GpStatus;
external 'GdipImageGetFrameCount@GdiPlus.dll stdcall';

function GdipImageGetFrameDimensionsList( image: GpImage; var dimensionID: TGuid; count: Integer ): GpStatus;
external 'GdipImageGetFrameDimensionsList@GdiPlus.dll stdcall';

function GdipImageSelectActiveFrame( image: GpImage; dimensionID: TGuid; frameIndex: Integer ): GpStatus;
external 'GdipImageSelectActiveFrame@GdiPlus.dll stdcall';

You have to use these types:

type
  TTimerProc = procedure( Wnd: HWND; Msg: UINT; TimerID: UINT_PTR; SysTime: DWORD );
  GpGraphics = Longword;
  GpImage = Longword;
  Status = (
    Ok,                        {  0 }
    GenericError,              {  1 }
    InvalidParameter,          {  2 }
    OutOfMemory,               {  3 }
    ObjectBusy,                {  4 }
    InsufficientBuffer,        {  5 }
    NotImplemented,            {  6 }
    Win32Error,                {  7 }
    WrongState,                {  8 }
    Aborted,                   {  9 }
    FileNotFound,              { 10 }
    ValueOverflow,             { 11 }
    AccessDenied,              { 12 }
    UnknownImageFormat,        { 13 }
    FontFamilyNotFound,        { 14 }
    FontStyleNotFound,         { 15 }
    NotTrueTypeFont,           { 16 }
    UnsupportedGdiplusVersion, { 17 }
    GdiplusNotInitialized,     { 18 }
    PropertyNotFound,          { 19 }
    PropertyNotSupported       { 20 }
  );
  GpStatus = Status;

  GdiplusStartupInput = record
   GdiplusVersion          : Cardinal; 
   DebugEventCallback      : Longword; 
   SuppressBackgroundThread: BOOL;     
   SuppressExternalCodecs  : BOOL;     
  end;                                 

  GdiplusStartupOutput = record
    NotificationHook  : Longword;
    NotificationUnhook: Longword;
  end;

And these vars:

  ClockImage: TPanel;
  TimerID: Integer;
  token: Longword;
  inputbuf: GdiplusStartupInput;
  outputbuf: GdiplusStartupOutput;
  graphics: GpGraphics;
  image: GpImage;
  status: GpStatus;
  count: Integer;
  dimensionID: array[ 0..1 ] of TGuid;
  iFrame: Integer;

Using this code, GDI+ gets initialized and we load and analyze the animated GIF:

  inputbuf.GdiplusVersion := 1;
  status := GdiplusStartup( token, inputbuf, outputbuf ); 
  { Assert( status ); }
  status := GdipCreateFromHWND( ClockImage.Handle, graphics ); 
  { Assert( status ); }
  status := GdipLoadImageFromFile( ExpandConstant( '{tmp}' ) + '\sand-clock-dribbble.gif', image );
  { Assert( status ); }
  { MsgBox( intToStr( image ), mbInformation, 1 ); }
  status := GdipDrawImageRect( graphics, image, 0, 0, ClockImage.Width, ClockImage.Height ); 
  { Assert( status ); }
  status := GdipImageGetFrameDimensionsCount( image, count );
  { Assert( status ); }
  { MsgBox( intToStr( count ), mbInformation, 1 ); }
  status := GdipImageGetFrameDimensionsList( image, dimensionID[ 0 ], count );
  { Assert( status ); }
  { MsgBox( intToStr( count ), mbInformation, 1 ); }
  status := GdipImageGetFrameCount( image, dimensionID[ 0 ], count );
  { Assert( status ); }
  { MsgBox( intToStr( count ), mbInformation, 1 );   }
  iFrame := 1
  status := GdipImageSelectActiveFrame( image, dimensionID[ 0 ], iFrame );

A timer is used to change the aimated GIFs frames using standard GDI+ functions:

procedure MyTimerProc( Arg1, Arg2, Arg3, Arg4: Longword );
begin
  if iFrame < count then begin
    iFrame := iFrame + 1;
  end else begin
    iFrame := 0
  end;

  status := GdipImageSelectActiveFrame( image, dimensionID[ 0 ], iFrame );
  status := GdipDrawImageRect( graphics, image, 0, 0, ClockImage.Width, ClockImage.Height ); 
end;

Full source code is available from here: https://github.com/FiveTechSoft/mod_harbour/blob/master/windows/win64/setup/modharbour.iss

To test it you can directly download the resulting EXE built from GitHub actions: https://github.com/FiveTechSoft/mod_harbour/actions/runs/102906695

Enjoy it!

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992