Pārlūkot izejas kodu

script: frame index and frame count

added new_frame, load_frame as well
circular17 5 gadi atpakaļ
vecāks
revīzija
0241757cd5

+ 1 - 0
lazpaint/dialog/umultiimage.pas

@@ -111,6 +111,7 @@ begin
       result.bmp := images[selectedIndex].bmp.Duplicate(AFormat = ifCur,True) as TBGRABitmap;
       result.bpp := images[selectedIndex].bpp;
       result.frameIndex := images[selectedIndex].frameIndex;
+      result.frameCount := images[selectedIndex].frameCount;
       result.isDuplicate:= images[selectedIndex].isDuplicate;
     end;
   end;

+ 23 - 6
lazpaint/image/uimage.pas

@@ -126,7 +126,7 @@ type
     ImageOffset: TPoint;
     Zoom: TZoom;
     CursorHotSpot: TPoint;
-    BPP, FrameIndex: integer;
+    BPP, FrameIndex, FrameCount: integer;
 
     // make copy
     function MakeLayeredBitmapCopy: TBGRALayeredBitmap;
@@ -216,13 +216,17 @@ type
     function AbleToSaveSelectionAsUTF8(AFilename: string): boolean;
     procedure SaveToFileUTF8(AFilename: string);
     procedure UpdateMultiImage(AOutputFilename: string = '');
-    procedure SetSavedFlag(ASavedBPP: integer = 0; ASavedFrameIndex: integer = 0);
+    procedure SetSavedFlag(ASavedBPP: integer = 0;
+                           ASavedFrameIndex: integer = 0;
+                           ASavedFrameCount: integer = 1);
     function IsFileModified: boolean;
     function IsFileModifiedAndSaved: boolean;
     procedure SaveOriginalToStream(AStream: TStream);
 
     function CheckCurrentLayerVisible: boolean;
     function CheckNoAction(ASilent: boolean = false): boolean;
+    function CanDuplicateFrame: boolean;
+    function CanHaveFrames: boolean;
     procedure ZoomFit;
 
     property CurrentState: TImageState read FCurrentState;
@@ -534,7 +538,7 @@ begin
     s := FileManager.CreateFileStream(AOutputFilename,fmCreate);
     try
       icoCur.SaveToStream(s);
-      SetSavedFlag(bpp, newFrameIndex);
+      SetSavedFlag(bpp, newFrameIndex, icoCur.Count);
     finally
       s.Free;
     end;
@@ -589,7 +593,7 @@ begin
     s := FileManager.CreateFileStream(AOutputFilename,fmCreate);
     try
       tiff.SaveToStream(s);
-      SetSavedFlag(bpp, newFrameIndex);
+      SetSavedFlag(bpp, newFrameIndex, tiff.Count);
     finally
       FreeAndNil(s);
     end;
@@ -633,7 +637,7 @@ begin
     s := FileManager.CreateFileStream(AOutputFilename,fmCreate);
     try
       gif.SaveToStream(s);
-      SetSavedFlag(bpp, newFrameIndex);
+      SetSavedFlag(bpp, newFrameIndex, gif.Count);
     finally
       FreeAndNil(s);
     end;
@@ -685,12 +689,13 @@ begin
   end;
 end;
 
-procedure TLazPaintImage.SetSavedFlag(ASavedBPP: integer; ASavedFrameIndex: integer);
+procedure TLazPaintImage.SetSavedFlag(ASavedBPP: integer; ASavedFrameIndex: integer; ASavedFrameCount: integer);
 var i: integer;
 begin
   FCurrentState.saved := true;
   self.BPP := ASavedBPP;
   self.FrameIndex := ASavedFrameIndex;
+  self.FrameCount := ASavedFrameCount;
   for i := 0 to FUndoList.Count-1 do
   begin
     TCustomImageDifference(FUndoList[i]).SavedBefore := (i = FUndoPos+1);
@@ -1474,6 +1479,16 @@ begin
   end;
 end;
 
+function TLazPaintImage.CanDuplicateFrame: boolean;
+begin
+  result := IsGif or IsTiff;
+end;
+
+function TLazPaintImage.CanHaveFrames: boolean;
+begin
+  result := IsGif or IsTiff or IsIconCursor;
+end;
+
 procedure TLazPaintImage.ZoomFit;
 begin
   if Assigned(Zoom) then Zoom.ZoomFit(Width,Height);
@@ -2126,6 +2141,8 @@ begin
   FUndoList := TList.Create;
   FUndoPos := -1;
   ImageOffset := Point(0,0);
+  FrameIndex := -1;
+  FrameCount := 0;
 end;
 
 destructor TLazPaintImage.Destroy;

+ 15 - 4
lazpaint/image/uimageaction.pas

@@ -23,6 +23,7 @@ type
     function GenericScriptFunction(AVars: TVariableSet): TScriptResult;
     function ScriptPutImage(AVars: TVariableSet): TScriptResult;
     function ScriptLayerFill(AVars: TVariableSet): TScriptResult;
+    function ScriptGetFrameIndex(AVars: TVariableSet): TScriptResult;
     procedure ReleaseSelection;
   public
     constructor Create(AInstance: TLazPaintCustomInstance);
@@ -145,11 +146,13 @@ begin
   Scripting.RegisterScriptFunction('LayerMergeOver',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('LayerRemoveCurrent',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('GetLayerCount',@GenericScriptFunction,ARegister);
+  Scripting.RegisterScriptFunction('GetFrameIndex',@ScriptGetFrameIndex,ARegister);
+  Scripting.RegisterScriptFunction('GetFrameCount',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('GetPixel',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('GetImageWidth',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('GetImageHeight',@GenericScriptFunction,ARegister);
-  Scripting.RegisterScriptFunction('PutImage',@GenericScriptFunction,ARegister);
-  Scripting.RegisterScriptFunction('LayerFill',@GenericScriptFunction,ARegister);
+  Scripting.RegisterScriptFunction('PutImage',@ScriptPutImage,ARegister);
+  Scripting.RegisterScriptFunction('LayerFill',@ScriptLayerFill,ARegister);
 end;
 
 constructor TImageActions.Create(AInstance: TLazPaintCustomInstance);
@@ -206,11 +209,10 @@ begin
   if f = 'LayerMergeOver' then MergeLayerOver else
   if f = 'LayerRemoveCurrent' then RemoveLayer else
   if f = 'GetLayerCount' then AVars.Integers['Result']:= Image.NbLayers else
+  if f = 'GetFrameCount' then AVars.Integers['Result']:= Image.FrameCount else
   if f = 'GetPixel' then AVars.Pixels['Result']:= GetPixel(AVars.Integers['X'],AVars.Integers['Y']) else
   if f = 'GetImageWidth' then AVars.Integers['Result']:= Image.Width else
   if f = 'GetImageHeight' then AVars.Integers['Result']:= Image.Height else
-  if f = 'PutImage' then result := ScriptPutImage(AVars) else
-  if f = 'LayerFill' then result := ScriptLayerFill(AVars) else
     result := srFunctionNotDefined;
 end;
 
@@ -315,6 +317,15 @@ begin
     result := srException;
 end;
 
+function TImageActions.ScriptGetFrameIndex(AVars: TVariableSet): TScriptResult;
+begin
+  if Image.FrameIndex <> -1 then
+    AVars.Integers['Result']:= Image.FrameIndex+1
+  else
+    AVars.Remove('Result');
+  result := srOk;
+end;
+
 procedure TImageActions.ClearAlpha;
 var c: TBGRAPixel;
     n: integer;

+ 1 - 1
lazpaint/lazpaintdialogs.inc

@@ -118,7 +118,7 @@ begin
   ChooseTool(ptHand);
   image.Assign(bitmapRepl, True, False);
   Image.CurrentFilenameUTF8 := '';
-  image.SetSavedFlag;
+  image.SetSavedFlag(0,-1,0);
   UpdateWindows;
   result := srOk;
 end;

+ 121 - 30
lazpaint/lazpaintmainform.pas

@@ -19,7 +19,7 @@ uses
   UImageObservation, UConfig, LCScaleDPI, UResourceStrings,
   UMenu, uscripting, ubrowseimages, UToolPolygon, UToolVectorial, LCVectorRectShapes,
 
-  laztablet, udarktheme;
+  laztablet, udarktheme, UScriptType;
 
 const
   MinPenWidthValue = 10;
@@ -813,6 +813,8 @@ type
     function ScriptFileOpen(AVars: TVariableSet): TScriptResult;
     function ScriptFileSaveAs(AVars: TVariableSet): TScriptResult;
     function ScriptFileSave({%H-}AVars: TVariableSet): TScriptResult;
+    function ScriptFileNewEntry(AVars: TVariableSet): TScriptResult;
+    function ScriptFileChooseEntry(AVars: TVariableSet): TScriptResult;
     function ScriptFileGetFilename(AVars: TVariableSet): TScriptResult;
     function ScriptFileReload({%H-}AVars: TVariableSet): TScriptResult;
     function ScriptFileLoadSelection(AVars: TVariableSet): TScriptResult;
@@ -841,7 +843,7 @@ type
     procedure InvalidatePicture;
     function TryOpenFileUTF8(filenameUTF8: string; AddToRecent: Boolean=True;
       ALoadedImage: PImageEntry = nil; ASkipDialogIfSingleImage: boolean = false;
-      AAllowDuplicate: boolean = false): Boolean;
+      AAllowDuplicate: boolean = false; AEntryToLoad: integer = -1): Boolean;
     function PictureCanvasOfs: TPoint;
     procedure UpdateLineCapBar;
     procedure UpdateColorToolbar(AUpdateColorDiff: boolean);
@@ -1071,7 +1073,7 @@ begin
 
   FImageActions.SetCurrentBitmap(TBGRABitmap.Create(Config.DefaultImageWidth,Config.DefaultImageHeight,Config.DefaultImageBackgroundColor), false);
   image.ClearUndo;
-  image.SetSavedFlag;
+  image.SetSavedFlag(0, -1, 0);
 
   ViewGrid.Checked := LazPaintInstance.GridVisible;
   ColorCurves.Visible := not LazPaintInstance.BlackAndWhite;
@@ -1133,6 +1135,8 @@ begin
   Scripting.RegisterScriptFunction('FileSave',@ScriptFileSave,ARegister);
   Scripting.RegisterScriptFunction('GetFileName',@ScriptFileGetFilename,ARegister);
   Scripting.RegisterScriptFunction('FileReload',@ScriptFileReload,ARegister);
+  Scripting.RegisterScriptFunction('FileChooseEntry',@ScriptFileChooseEntry,ARegister);
+  Scripting.RegisterScriptFunction('FileNewEntry',@ScriptFileNewEntry,ARegister);
   Scripting.RegisterScriptFunction('FileLoadSelection',@ScriptFileLoadSelection,ARegister);
   Scripting.RegisterScriptFunction('FileSaveSelectionAs',@ScriptFileSaveSelectionAs,ARegister);
   Scripting.RegisterScriptFunction('EditPasteAsNew',@ScriptEditPasteAsNew,ARegister);
@@ -1303,7 +1307,7 @@ begin
     begin
       FLazPaintInstance.ShowTopmost(topInfo);
       if TryOpenFileUTF8(AVars.GetString(vFilename), true, nil,
-           false, AVars.Booleans['AllowDuplicate']) then
+           false, false) then
         result := srOk
       else
         result := srException;
@@ -1575,6 +1579,99 @@ begin
     end;
 end;
 
+function TFMain.ScriptFileNewEntry(AVars: TVariableSet): TScriptResult;
+var w,h: integer;
+  topInfo: TTopMostInfo;
+  backColor: TBGRAPixel;
+begin
+  if (Image.currentFilenameUTF8='') or not Image.CanHaveFrames then exit(srException);
+  topInfo.defined:= false;
+  if Image.IsFileModified and not AVars.Booleans['IgnoreModified'] then
+  begin
+    topInfo := FLazPaintInstance.HideTopmost;
+    case LazPaintInstance.SaveQuestion(rsOpen) of
+    IDYES: begin
+             result := Scripting.CallScriptFunction('FileSave');
+             if result <> srOk then
+             begin
+               FLazPaintInstance.ShowTopmost(topInfo);
+               exit;
+             end;
+           end;
+    IDCANCEL: begin
+                FLazPaintInstance.ShowTopmost(topInfo);
+                result := srCancelledByUser;
+                exit;
+              end;
+    end;
+  end;
+  FLazPaintInstance.ShowTopmost(topInfo);
+
+  if Image.CanDuplicateFrame then
+  begin
+    w := Image.Width;
+    h := Image.Height;
+  end else
+  begin
+    w := 0;
+    h := 0;
+  end;
+  if AVars.IsDefined('Width') then w := AVars.Integers['Width'];
+  if AVars.IsDefined('Height') then h := AVars.Integers['Height'];
+  if (h <= 0) or (w <= 0) then exit(srInvalidParameters);
+  if Image.IsGif and ((w <> Image.Width) or (h <> Image.Height)) then exit(srInvalidParameters);
+  backColor := AVars.Pixels['BackColor'];
+
+  Image.Assign(TBGRABitmap.Create(w,h,backColor),true,false);
+  Image.SetSavedFlag(0,-1,Image.FrameCount);
+  result := srOk;
+end;
+
+function TFMain.ScriptFileChooseEntry(AVars: TVariableSet): TScriptResult;
+var
+  topInfo: TTopMostInfo;
+  entryToLoad: integer;
+  vEntryIndex: TScriptVariableReference;
+begin
+  if (Image.currentFilenameUTF8='') or not Image.CanHaveFrames then exit(srException);
+  topInfo.defined:= false;
+  if Image.IsFileModified and not AVars.Booleans['IgnoreModified'] then
+  begin
+    topInfo := FLazPaintInstance.HideTopmost;
+    case LazPaintInstance.SaveQuestion(rsOpen) of
+    IDYES: begin
+             result := Scripting.CallScriptFunction('FileSave');
+             if result <> srOk then
+             begin
+               FLazPaintInstance.ShowTopmost(topInfo);
+               exit;
+             end;
+           end;
+    IDCANCEL: begin
+                FLazPaintInstance.ShowTopmost(topInfo);
+                result := srCancelledByUser;
+                exit;
+              end;
+    end;
+  end;
+  FLazPaintInstance.ShowTopmost(topInfo);
+  vEntryIndex := AVars.GetVariable('EntryIndex');
+  if AVars.IsReferenceDefined(vEntryIndex) then
+  begin
+    entryToLoad := AVars.GetInteger(vEntryIndex)-1;
+    if entryToLoad < 0 then entryToLoad := -1;
+  end
+  else entryToLoad := -1;
+  if TryOpenFileUTF8(Image.currentFilenameUTF8, false, nil,
+       true, Image.CanDuplicateFrame, entryToLoad) then
+  begin
+    AVars.Integers['Result'] := Image.FrameIndex;
+    result := srOk;
+  end
+  else
+    result := srException;
+end;
+
 function TFMain.ScriptFileGetFilename(AVars: TVariableSet): TScriptResult;
 begin
   if Image.currentFilenameUTF8='' then
@@ -1862,7 +1959,8 @@ begin
       exit;
     end;
   end;
-  if TryOpenFileUTF8(Image.CurrentFilenameUTF8) then
+  if TryOpenFileUTF8(Image.CurrentFilenameUTF8,false,nil,
+                     true,false,Image.FrameIndex) then
     result := srOk
   else
     result := srException;
@@ -2704,7 +2802,7 @@ begin
         end;
         image.Assign(bmp,true,false);
         Image.CurrentFilenameUTF8 := '';
-        image.SetSavedFlag;
+        image.SetSavedFlag(0, -1, 0);
         result := srOk;
       end
        else
@@ -3084,9 +3182,7 @@ procedure TFMain.FileChooseEntryExecute(Sender: TObject);
 var
   openParams: TVariableSet;
 begin
-  openParams := TVariableSet.Create('FileOpen');
-  openParams.AddString('FileName',Image.currentFilenameUTF8);
-  openParams.AddBoolean('AllowDuplicate',true);
+  openParams := TVariableSet.Create('FileChooseEntry');
   Scripting.CallScriptFunction(openParams);
   openParams.Free;
 end;
@@ -3634,7 +3730,7 @@ end;
 
 function TFMain.TryOpenFileUTF8(filenameUTF8: string; AddToRecent: Boolean;
      ALoadedImage: PImageEntry; ASkipDialogIfSingleImage: boolean;
-     AAllowDuplicate: boolean): Boolean;
+     AAllowDuplicate: boolean; AEntryToLoad: integer): Boolean;
 var
   newPicture: TImageEntry;
   format: TBGRAImageFormat;
@@ -3649,7 +3745,7 @@ var
     ShowNoPicture;
     Image.OnImageChanged.NotifyObservers;
   end;
-  procedure EndImport(BPP: integer = 0; frameIndex: integer = 0);
+  procedure EndImport(BPP: integer = 0; frameIndex: integer = 0; frameCount: integer = 1);
   begin
     if AddToRecent then
     begin
@@ -3658,7 +3754,7 @@ var
     end;
     Image.CurrentFilenameUTF8 := filenameUTF8;
     image.ClearUndo;
-    image.SetSavedFlag(BPP, frameIndex);
+    image.SetSavedFlag(BPP, frameIndex, frameCount);
     ToolManager.ToolOpen;
     ZoomFitIfTooBig;
     ToolHotSpotUpdate(nil);
@@ -3679,7 +3775,7 @@ var
         end;
       image.Assign(newPicture.bmp,True, false);
       newPicture.bmp := nil;
-      EndImport(newPicture.bpp, newPicture.frameIndex);
+      EndImport(newPicture.bpp, newPicture.frameIndex, newPicture.frameCount);
     end else FreeAndNil(newPicture.bmp);
   end;
 
@@ -3720,6 +3816,8 @@ begin
     begin
       newPicture.bmp := GetRawFileImage(filenameUTF8);
       newPicture.bpp := 0;
+      newPicture.frameIndex:= 0;
+      newPicture.frameCount:= 1;
       ImportNewPicture;
     end else
     if format in[ifIco,ifCur] then
@@ -3728,33 +3826,26 @@ begin
       ImportNewPicture;
     end
     else
-    if format in[ifIco,ifTiff] then
+    if format in[ifIco,ifGif,ifTiff] then
     begin
-      if (format = ifTiff) and AAllowDuplicate and (Image.FrameIndex <> -1) then dupIndex := Image.FrameIndex else dupIndex := -1;
-      newPicture := ShowPreviewDialog(LazPaintInstance, FilenameUTF8, 'TIFF',
-        ASkipDialogIfSingleImage, dupIndex);
+      if AEntryToLoad <> -1 then
+        newPicture := LoadFlatImageUTF8(FilenameUTF8, AEntryToLoad) else
+      begin
+        if (format in[ifGif,ifTiff]) and AAllowDuplicate and (Image.FrameIndex <> -1) then
+          dupIndex := Image.FrameIndex else dupIndex := -1;
+        newPicture := ShowPreviewDialog(LazPaintInstance, FilenameUTF8,
+          GetImageFormatName(format),ASkipDialogIfSingleImage, dupIndex);
+      end;
       if newPicture.isDuplicate then
       begin
         newPicture.FreeAndNil;
         Image.FrameIndex:= newPicture.frameIndex;
+        Image.FrameCount:= newPicture.frameCount;
         Image.OnImageChanged.NotifyObservers;
       end
       else ImportNewPicture;
     end
     else
-    if format = ifGif then
-    begin
-      if AAllowDuplicate and (Image.FrameIndex <> -1) then dupIndex := Image.FrameIndex else dupIndex := -1;
-      newPicture := ShowPreviewDialog(LazPaintInstance, FilenameUTF8, rsAnimatedGIF,
-        ASkipDialogIfSingleImage, dupIndex);
-      if newPicture.isDuplicate then
-      begin
-        newPicture.FreeAndNil;
-        Image.FrameIndex:= newPicture.frameIndex;
-        Image.OnImageChanged.NotifyObservers;
-      end
-      else ImportNewPicture;
-    end else
     begin
       StartImport;
       image.LoadFromFileUTF8(filenameUTF8);

+ 3 - 2
lazpaint/lazpainttype.pas

@@ -117,7 +117,7 @@ type
     TImageEntry = object
       bmp: TBGRABitmap;
       bpp: integer;
-      frameIndex: integer;
+      frameIndex, frameCount: integer;
       isDuplicate: boolean;
       class function Empty: TImageEntry; static;
       class function NewFrameIndex: integer; static;
@@ -528,7 +528,8 @@ class function TImageEntry.Empty: TImageEntry;
 begin
   result.bmp := nil;
   result.bpp := 0;
-  result.frameIndex := 0;
+  result.frameIndex := -1;
+  result.frameCount := 0;
   result.isDuplicate:= false;
 end;
 

+ 1 - 1
lazpaint/ubrushtype.pas

@@ -167,7 +167,7 @@ begin
     FSourceImage := nil;
     try
       if FileName <> '' then
-        FSourceImage := LoadFlatImageUTF8(FileName, True).bmp
+        FSourceImage := LoadFlatImageUTF8(FileName, 0).bmp
       else
       if Stream64<> '' then
       begin

+ 6 - 0
lazpaint/uimagepreview.pas

@@ -639,6 +639,7 @@ begin
   if (index < 0) or (index >= GetEntryCount) then
     raise exception.Create('Index out of bounds');
   result := TImageEntry.Empty;
+  result.frameCount:= EntryCount;
   try
     if Assigned(FIconCursor) then
     begin
@@ -688,6 +689,8 @@ begin
     raise exception.Create('Index out of bounds');
   if Filename = '' then raise exception.create('Filename undefined');
 
+  AEntry.frameCount:= GetEntryCount;
+
   if Assigned(FTiff) then
   begin
     addedTiff := TTiff.Create;
@@ -1037,6 +1040,7 @@ begin
               LazPaintInstance.Image.FrameIndex := TImageEntry.NewFrameIndex;
             LazPaintInstance.Image.OnImageChanged.NotifyObservers;
           end;
+          dec(LazPaintInstance.Image.FrameCount);
         except on ex: Exception do
           begin
             FileManager.CancelStreamAndFree(outputStream);
@@ -1155,6 +1159,7 @@ var tx,ty,bpp: integer; back: TBGRAPixel;
 begin
   FinishUpdatePreview;
   result := TImageEntry.Empty;
+  result.frameCount := GetEntryCount;
 
   if Assigned(FIconCursor) then
   begin
@@ -1213,6 +1218,7 @@ begin
   if Assigned(FSingleImage) then
   begin
     result.bmp := FSingleImage;
+    result.frameIndex:= 0;
     FSingleImage := nil;
   end else
   if Assigned(FAnimatedGif) then

+ 95 - 27
lazpaint/uloadimage.pas

@@ -7,7 +7,7 @@ interface
 uses
   Classes, SysUtils, LazPaintType, BGRABitmap, BGRALayers, BGRASVGOriginal;
 
-function LoadFlatImageUTF8(AFilename: string; ASkipDialog: boolean = false): TImageEntry;
+function LoadFlatImageUTF8(AFilename: string; AEntryToLoad: integer = -1): TImageEntry;
 procedure FreeMultiImage(var images: ArrayOfImageEntry);
 function AbleToLoadUTF8(AFilename: string): boolean;
 function LoadSVGImageUTF8(AFilename: string): TBGRALayeredBitmap;
@@ -20,21 +20,62 @@ uses FileUtil, BGRAAnimatedGif, Graphics, UMultiImage,
   UFileSystem, BGRAIconCursor, BGRAReadTiff,
   Dialogs, math, URaw;
 
+function LoadIcoEntryFromStream(AStream: TStream; AIndex: integer): TImageEntry;
+var ico: TBGRAIconCursor;
+begin
+  if AIndex < 0 then raise exception.Create('Index out of bounds');
+  result := TImageEntry.Empty;
+  ico := TBGRAIconCursor.Create;
+  try
+    ico.LoadFromStream(AStream);
+    result.bmp := ico.GetBitmap(AIndex) as TBGRABitmap;
+    result.bmp.Caption := IntTostr(ico.Width[AIndex])+'x'+IntToStr(ico.Height[AIndex])+' '+IntToStr(ico.BitDepth[AIndex])+'bit';
+    if Assigned(result.bmp.XorMask) then result.bmp.XorMask.Caption := result.bmp.Caption + ' (xor)';
+    result.bpp := ico.BitDepth[AIndex];
+    result.frameIndex:= AIndex;
+    result.frameCount:= ico.Count;
+  finally
+    ico.Free;
+  end;
+end;
+
 function LoadIcoMultiImageFromStream(AStream: TStream): ArrayOfImageEntry;
 var ico: TBGRAIconCursor; i: integer;
 begin
   ico := TBGRAIconCursor.Create;
-  ico.LoadFromStream(AStream);
-  setlength(result,ico.Count);
-  for i := 0 to ico.Count-1 do
-  begin
-    result[i].bmp := ico.GetBitmap(i) as TBGRABitmap;
-    result[i].bmp.Caption := IntTostr(ico.Width[i])+'x'+IntToStr(ico.Height[i])+' '+IntToStr(ico.BitDepth[i])+'bit';
-    if Assigned(result[i].bmp.XorMask) then result[i].bmp.XorMask.Caption := result[i].bmp.Caption + ' (xor)';
-    result[i].bpp := ico.BitDepth[i];
-    result[i].frameIndex:= i;
+  try
+    ico.LoadFromStream(AStream);
+    setlength(result,ico.Count);
+    for i := 0 to ico.Count-1 do
+    begin
+      result[i].bmp := ico.GetBitmap(i) as TBGRABitmap;
+      result[i].bmp.Caption := IntTostr(ico.Width[i])+'x'+IntToStr(ico.Height[i])+' '+IntToStr(ico.BitDepth[i])+'bit';
+      if Assigned(result[i].bmp.XorMask) then result[i].bmp.XorMask.Caption := result[i].bmp.Caption + ' (xor)';
+      result[i].bpp := ico.BitDepth[i];
+      result[i].frameIndex:= i;
+      result[i].frameCount:= ico.Count;
+    end;
+  finally
+    ico.Free;
+  end;
+end;
+
+function LoadGifEntryFromStream(AStream: TStream; AIndex: integer): TImageEntry;
+var gif: TBGRAAnimatedGif;
+begin
+  if AIndex < 0 then raise exception.Create('Index out of bounds');
+  result := TImageEntry.Empty;
+  gif := TBGRAAnimatedGif.Create(AStream);
+  try
+    gif.CurrentImage:= AIndex;
+    result.bmp := gif.MemBitmap.Duplicate as TBGRABitmap;
+    result.bmp.Caption := 'Frame'+IntToStr(AIndex+1);
+    result.frameIndex := AIndex;
+    result.frameCount := gif.Count;
+  finally
+    gif.Free;
   end;
-  ico.Free;
+
 end;
 
 function LoadGifMultiImageFromStream(AStream: TStream): ArrayOfImageEntry;
@@ -47,15 +88,33 @@ begin
     begin
       gif.CurrentImage:= i;
       result[i].bmp := gif.MemBitmap.Duplicate as TBGRABitmap;
-      result[i].bmp.Caption := 'Frame'+IntToStr(i);
+      result[i].bmp.Caption := 'Frame'+IntToStr(i+1);
       result[i].bpp := 0;
       result[i].frameIndex := i;
+      result[i].frameCount := gif.Count;
     end;
   finally
     gif.Free;
   end;
 end;
 
+function LoadTiffEntryFromStream(AStream: TStream; AIndex: integer): TImageEntry;
+var tiff: TBGRAReaderTiff;
+begin
+  if AIndex < 0 then raise exception.Create('Index out of bounds');
+  result := TImageEntry.Empty;
+  tiff := TBGRAReaderTiff.Create;
+  try
+    tiff.LoadFromStream(AStream);
+    result.bmp := (tiff.Images[AIndex].Img as TBGRABitmap).Duplicate as TBGRABitmap;
+    result.bmp.Caption := 'Image'+IntToStr(AIndex+1);
+    result.frameIndex:= AIndex;
+    result.frameCount:= tiff.ImageCount;
+  finally
+    tiff.Free;
+  end;
+end;
+
 function LoadTiffMultiImageFromStream(AStream: TStream): ArrayOfImageEntry;
 var tiff: TBGRAReaderTiff;
   i: Integer;
@@ -67,9 +126,10 @@ begin
     for i := 0 to tiff.ImageCount-1 do
     begin
       result[i].bmp := (tiff.Images[i].Img as TBGRABitmap).Duplicate as TBGRABitmap;
-      result[i].bmp.Caption := 'Image'+IntToStr(i);
+      result[i].bmp.Caption := 'Image'+IntToStr(i+1);
       result[i].bpp := 0;
       result[i].frameIndex:= i;
+      result[i].frameCount:= tiff.ImageCount;
     end;
   finally
     tiff.Free;
@@ -157,7 +217,7 @@ begin
   end;
 end;
 
-function LoadFlatImageUTF8(AFilename: string; ASkipDialog: boolean): TImageEntry;
+function LoadFlatImageUTF8(AFilename: string; AEntryToLoad: integer): TImageEntry;
 var
   formMultiImage: TFMultiImage;
   multi: ArrayOfImageEntry;
@@ -195,25 +255,33 @@ begin
     end else
     if format in[ifIco,ifCur] then
     begin
-      multi := LoadIcoMultiImageFromStream(s);
-      if ASkipDialog then
-      begin
-        result := multi[0];
-        multi[0] := TImageEntry.Empty;
-        FreeMultiImage(multi);
-      end
+      if AEntryToLoad <> -1 then
+        result := LoadIcoEntryFromStream(s, AEntryToLoad)
       else
+      begin
+        multi := LoadIcoMultiImageFromStream(s);
         ChooseMulti(False);
+      end;
     end else
-    if (format = ifGif) and not ASkipDialog then
+    if format = ifGif then
     begin
-      multi := LoadGifMultiImageFromStream(s);
-      ChooseMulti(True);
+      if AEntryToLoad <> -1 then
+        result := LoadGifEntryFromStream(s, AEntryToLoad)
+      else
+      begin
+        multi := LoadGifMultiImageFromStream(s);
+        ChooseMulti(True);
+      end;
     end else
-    if (format = ifTiff) and not ASkipDialog then
+    if format = ifTiff then
     begin
-      multi := LoadTiffMultiImageFromStream(s);
-      ChooseMulti(True);
+      if AEntryToLoad <> -1 then
+        result := LoadTiffEntryFromStream(s, AEntryToLoad)
+      else
+      begin
+        multi := LoadTiffMultiImageFromStream(s);
+        ChooseMulti(True);
+      end;
     end else
     if format = ifLazPaint then
     begin

+ 12 - 0
lazpaintscripts/lazpaint/image.py

@@ -15,6 +15,18 @@ def get_height():
 def get_layer_count():
   return command.send("GetLayerCount?")
 
+def get_frame_index():
+  return command.send("GetFrameIndex?")
+
+def get_frame_count():
+  return command.send("GetFrameCount?")
+
+def load_frame(frame_index=None, ignore_modified=False):
+  return command.send("FileChooseEntry?", EntryIndex=frame_index, IgnoreModified=ignore_modified)
+
+def new_frame(width=None, height=None, back_color=colors.TRANSPARENT, ignore_modified=False):
+  command.send("FileNewEntry", Width=width, Height=height, BackColor=back_color, IgnoreModified=ignore_modified)
+
 def open(file_name=None, ignore_modified=False):
   command.send("FileOpen", FileName=file_name, IgnoreModified=ignore_modified)
 

+ 34 - 0
lazpaintscripts/test_tiff_multi.py

@@ -0,0 +1,34 @@
+from lazpaint import image, layer, dialog
+
+def show_frame():
+  dialog.show_message("Frame " + str(image.get_frame_index()) + '/' + str(image.get_frame_count()) )
+
+show_frame()
+
+image.new(128,128, 0)
+image.save_as("test_multi.tiff")
+show_frame()
+
+image.new_frame()
+layer.fill(64)
+image.save()
+show_frame()
+
+image.new_frame()
+layer.fill(128)
+image.save()
+show_frame()
+
+image.new_frame()
+layer.fill(192)
+image.save()
+show_frame()
+
+image.new_frame()
+layer.fill(255)
+image.save()
+show_frame()
+
+image.load_frame(1)
+show_frame()
+