Inno Setup animated GIFs using GDI+?

Viewed 376

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 ?

1 Answers

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!

Related