Kaynağa Gözat

duplicate frame in GIF or TIFF

circular17 6 yıl önce
ebeveyn
işleme
b52611f411

+ 1 - 0
lazpaint/dialog/umultiimage.pas

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

+ 16 - 2
lazpaint/dialog/upreviewdialog.pas

@@ -20,9 +20,11 @@ type
     procedure FormDestroy(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
   private
   private
     FPreview: TImagePreview;
     FPreview: TImagePreview;
+    function GetDuplicateSourceIndex: integer;
     function GetEntryCount: integer;
     function GetEntryCount: integer;
     function GetFilename: string;
     function GetFilename: string;
     function GetLazPaintInstance: TLazPaintCustomInstance;
     function GetLazPaintInstance: TLazPaintCustomInstance;
+    procedure SetDuplicateSourceIndex(AValue: integer);
     procedure SetFilename(AValue: string);
     procedure SetFilename(AValue: string);
     procedure PreviewValidate(Sender: TObject);
     procedure PreviewValidate(Sender: TObject);
     procedure PreviewEscape(Sender: TObject);
     procedure PreviewEscape(Sender: TObject);
@@ -32,21 +34,23 @@ type
     property Filename: string read GetFilename write SetFilename;
     property Filename: string read GetFilename write SetFilename;
     property LazPaintInstance: TLazPaintCustomInstance read GetLazPaintInstance write SetLazPaintInstance;
     property LazPaintInstance: TLazPaintCustomInstance read GetLazPaintInstance write SetLazPaintInstance;
     property EntryCount: integer read GetEntryCount;
     property EntryCount: integer read GetEntryCount;
+    property DuplicateSourceIndex: integer read GetDuplicateSourceIndex write SetDuplicateSourceIndex;
   end;
   end;
 
 
 var
 var
   FPreviewDialog: TFPreviewDialog;
   FPreviewDialog: TFPreviewDialog;
 
 
 function ShowPreviewDialog(AInstance: TLazPaintCustomInstance; AFilename: string; ATitle: string = '';
 function ShowPreviewDialog(AInstance: TLazPaintCustomInstance; AFilename: string; ATitle: string = '';
-  ASkipIfSingleImage: boolean = false): TImageEntry;
+  ASkipIfSingleImage: boolean = false; ADuplicateSourceIndex: integer = -1): TImageEntry;
 
 
 implementation
 implementation
 
 
 function ShowPreviewDialog(AInstance: TLazPaintCustomInstance; AFilename: string; ATitle: string;
 function ShowPreviewDialog(AInstance: TLazPaintCustomInstance; AFilename: string; ATitle: string;
-  ASkipIfSingleImage: boolean): TImageEntry;
+  ASkipIfSingleImage: boolean; ADuplicateSourceIndex: integer): TImageEntry;
 var f: TFPreviewDialog;
 var f: TFPreviewDialog;
 begin
 begin
   f := TFPreviewDialog.Create(nil);
   f := TFPreviewDialog.Create(nil);
+  f.DuplicateSourceIndex := ADuplicateSourceIndex;
   f.LazPaintInstance := AInstance;
   f.LazPaintInstance := AInstance;
   if ATitle <> '' then f.Caption := ATitle;
   if ATitle <> '' then f.Caption := ATitle;
   f.Filename:= AFilename;
   f.Filename:= AFilename;
@@ -90,11 +94,21 @@ begin
     result := 0;
     result := 0;
 end;
 end;
 
 
+function TFPreviewDialog.GetDuplicateSourceIndex: integer;
+begin
+  result := FPreview.DuplicateEntrySourceIndex;
+end;
+
 function TFPreviewDialog.GetLazPaintInstance: TLazPaintCustomInstance;
 function TFPreviewDialog.GetLazPaintInstance: TLazPaintCustomInstance;
 begin
 begin
   result := FPreview.LazPaintInstance;
   result := FPreview.LazPaintInstance;
 end;
 end;
 
 
+procedure TFPreviewDialog.SetDuplicateSourceIndex(AValue: integer);
+begin
+  FPreview.DuplicateEntrySourceIndex:= AValue;
+end;
+
 procedure TFPreviewDialog.SetFilename(AValue: string);
 procedure TFPreviewDialog.SetFilename(AValue: string);
 begin
 begin
   FPreview.Filename := AValue;
   FPreview.Filename := AValue;

+ 29 - 8
lazpaint/lazpaintmainform.pas

@@ -836,8 +836,9 @@ type
 
 
     procedure PaintPictureNow;
     procedure PaintPictureNow;
     procedure InvalidatePicture;
     procedure InvalidatePicture;
-    function TryOpenFileUTF8(filenameUTF8: string; AddToRecent: Boolean=True; ALoadedImage: PImageEntry = nil;
-      ASkipDialogIfSingleImage: boolean = false): Boolean;
+    function TryOpenFileUTF8(filenameUTF8: string; AddToRecent: Boolean=True;
+      ALoadedImage: PImageEntry = nil; ASkipDialogIfSingleImage: boolean = false;
+      AAllowDuplicate: boolean = false): Boolean;
     function PictureCanvasOfs: TPoint;
     function PictureCanvasOfs: TPoint;
     procedure UpdateLineCapBar;
     procedure UpdateLineCapBar;
     procedure UpdateColorToolbar(AUpdateColorDiff: boolean);
     procedure UpdateColorToolbar(AUpdateColorDiff: boolean);
@@ -1297,7 +1298,8 @@ begin
     if AVars.IsReferenceDefined(vFileName) then
     if AVars.IsReferenceDefined(vFileName) then
     begin
     begin
       FLazPaintInstance.ShowTopmost(topInfo);
       FLazPaintInstance.ShowTopmost(topInfo);
-      if TryOpenFileUTF8(AVars.GetString(vFilename), true, nil) then
+      if TryOpenFileUTF8(AVars.GetString(vFilename), true, nil,
+           false, AVars.Booleans['AllowDuplicate']) then
         result := srOk
         result := srOk
       else
       else
         result := srException;
         result := srException;
@@ -3023,6 +3025,7 @@ var
 begin
 begin
   openParams := TVariableSet.Create('FileOpen');
   openParams := TVariableSet.Create('FileOpen');
   openParams.AddString('FileName',Image.currentFilenameUTF8);
   openParams.AddString('FileName',Image.currentFilenameUTF8);
+  openParams.AddBoolean('AllowDuplicate',true);
   Scripting.CallScriptFunction(openParams);
   Scripting.CallScriptFunction(openParams);
   openParams.Free;
   openParams.Free;
 end;
 end;
@@ -3547,10 +3550,12 @@ begin
 end;
 end;
 
 
 function TFMain.TryOpenFileUTF8(filenameUTF8: string; AddToRecent: Boolean;
 function TFMain.TryOpenFileUTF8(filenameUTF8: string; AddToRecent: Boolean;
-     ALoadedImage: PImageEntry; ASkipDialogIfSingleImage: boolean): Boolean;
+     ALoadedImage: PImageEntry; ASkipDialogIfSingleImage: boolean;
+     AAllowDuplicate: boolean): Boolean;
 var
 var
   newPicture: TImageEntry;
   newPicture: TImageEntry;
   format: TBGRAImageFormat;
   format: TBGRAImageFormat;
+  dupIndex: Integer;
 
 
   procedure StartImport;
   procedure StartImport;
   begin
   begin
@@ -3631,14 +3636,30 @@ begin
     else
     else
     if format in[ifIco,ifTiff] then
     if format in[ifIco,ifTiff] then
     begin
     begin
-      newPicture := ShowPreviewDialog(LazPaintInstance, FilenameUTF8, 'TIFF', ASkipDialogIfSingleImage);
-      ImportNewPicture;
+      if (format = ifTiff) and AAllowDuplicate and (Image.FrameIndex <> -1) then dupIndex := Image.FrameIndex else dupIndex := -1;
+      newPicture := ShowPreviewDialog(LazPaintInstance, FilenameUTF8, 'TIFF',
+        ASkipDialogIfSingleImage, dupIndex);
+      if newPicture.isDuplicate then
+      begin
+        newPicture.FreeAndNil;
+        Image.FrameIndex:= newPicture.frameIndex;
+        Image.OnImageChanged.NotifyObservers;
+      end
+      else ImportNewPicture;
     end
     end
     else
     else
     if format = ifGif then
     if format = ifGif then
     begin
     begin
-      newPicture := ShowPreviewDialog(LazPaintInstance, FilenameUTF8, rsAnimatedGIF, ASkipDialogIfSingleImage);
-      ImportNewPicture;
+      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
     end else
     begin
     begin
       StartImport;
       StartImport;

+ 2 - 0
lazpaint/lazpainttype.pas

@@ -118,6 +118,7 @@ type
       bmp: TBGRABitmap;
       bmp: TBGRABitmap;
       bpp: integer;
       bpp: integer;
       frameIndex: integer;
       frameIndex: integer;
+      isDuplicate: boolean;
       class function Empty: TImageEntry; static;
       class function Empty: TImageEntry; static;
       class function NewFrameIndex: integer; static;
       class function NewFrameIndex: integer; static;
       procedure FreeAndNil;
       procedure FreeAndNil;
@@ -527,6 +528,7 @@ begin
   result.bmp := nil;
   result.bmp := nil;
   result.bpp := 0;
   result.bpp := 0;
   result.frameIndex := 0;
   result.frameIndex := 0;
+  result.isDuplicate:= false;
 end;
 end;
 
 
 class function TImageEntry.NewFrameIndex: integer;
 class function TImageEntry.NewFrameIndex: integer;

+ 4 - 0
lazpaint/release/bin/i18n/lazpaint.de.po

@@ -1729,6 +1729,10 @@ msgstr ""
 msgid "Dither layer using palette"
 msgid "Dither layer using palette"
 msgstr "Dither auf Palette dieser Ebene anwenden"
 msgstr "Dither auf Palette dieser Ebene anwenden"
 
 
+#: uresourcestrings.rsduplicateimage
+msgid "Duplicate image"
+msgstr "Bild duplizieren"
+
 #: uresourcestrings.rseditmask
 #: uresourcestrings.rseditmask
 msgid "Edit mask"
 msgid "Edit mask"
 msgstr "Maske bearbeiten"
 msgstr "Maske bearbeiten"

+ 4 - 0
lazpaint/release/bin/i18n/lazpaint.es.po

@@ -1698,6 +1698,10 @@ msgstr "El directorio no esta vacío"
 msgid "Dither layer using palette"
 msgid "Dither layer using palette"
 msgstr "Entramado de capa usando paleta"
 msgstr "Entramado de capa usando paleta"
 
 
+#: uresourcestrings.rsduplicateimage
+msgid "Duplicate image"
+msgstr "Duplicar imagen"
+
 #: uresourcestrings.rseditmask
 #: uresourcestrings.rseditmask
 msgctxt "uresourcestrings.rseditmask"
 msgctxt "uresourcestrings.rseditmask"
 msgid "Edit mask"
 msgid "Edit mask"

+ 4 - 0
lazpaint/release/bin/i18n/lazpaint.fr.po

@@ -1720,6 +1720,10 @@ msgstr "Le dossier n'est pas vide"
 msgid "Dither layer using palette"
 msgid "Dither layer using palette"
 msgstr "Tramer le calque en utilisant la palette"
 msgstr "Tramer le calque en utilisant la palette"
 
 
+#: uresourcestrings.rsduplicateimage
+msgid "Duplicate image"
+msgstr "Dupliquer l'image"
+
 #: uresourcestrings.rseditmask
 #: uresourcestrings.rseditmask
 msgctxt "uresourcestrings.rseditmask"
 msgctxt "uresourcestrings.rseditmask"
 msgid "Edit mask"
 msgid "Edit mask"

+ 4 - 0
lazpaint/release/bin/i18n/lazpaint.po

@@ -1684,6 +1684,10 @@ msgstr ""
 msgid "Dither layer using palette"
 msgid "Dither layer using palette"
 msgstr ""
 msgstr ""
 
 
+#: uresourcestrings.rsduplicateimage
+msgid "Duplicate image"
+msgstr ""
+
 #: uresourcestrings.rseditmask
 #: uresourcestrings.rseditmask
 msgid "Edit mask"
 msgid "Edit mask"
 msgstr ""
 msgstr ""

+ 4 - 0
lazpaint/release/bin/i18n/lazpaint.pt_BR.po

@@ -1706,6 +1706,10 @@ msgstr ""
 msgid "Dither layer using palette"
 msgid "Dither layer using palette"
 msgstr ""
 msgstr ""
 
 
+#: uresourcestrings.rsduplicateimage
+msgid "Duplicate image"
+msgstr "Duplicar imagem"
+
 #: uresourcestrings.rseditmask
 #: uresourcestrings.rseditmask
 msgid "Edit mask"
 msgid "Edit mask"
 msgstr "Editar máscara"
 msgstr "Editar máscara"

+ 1 - 0
lazpaint/release/changelog

@@ -104,6 +104,7 @@ lazpaint (7.0.7) stable; urgency=low
   * fix transform when copying shapes between layers
   * fix transform when copying shapes between layers
   * fix crop layer with layer offset
   * fix crop layer with layer offset
   * clear layer when deleting it in shape editor
   * clear layer when deleting it in shape editor
+  * duplicate entry in GIF or TIFF
 
 
 -- circular <[email protected]>  Wed, 10 Oct 2019 17:37:00 +0100
 -- circular <[email protected]>  Wed, 10 Oct 2019 17:37:00 +0100
 
 

+ 127 - 23
lazpaint/uimagepreview.pas

@@ -33,13 +33,14 @@ type
     FTiff: TTiff;                     //has entries
     FTiff: TTiff;                     //has entries
     FIconCursor: TBGRAIconCursor;     //has entries
     FIconCursor: TBGRAIconCursor;     //has entries
     FThumbnails: TBGRABitmapList;
     FThumbnails: TBGRABitmapList;
+    FDuplicateEntrySourceIndex: integer;
 
 
     FSelectedMenuIndex: integer;
     FSelectedMenuIndex: integer;
     FImageMenu: array of record
     FImageMenu: array of record
                  Area, IconArea: TRect;
                  Area, IconArea: TRect;
                  DeleteArea: TRect;
                  DeleteArea: TRect;
                  FrameIndex: integer;
                  FrameIndex: integer;
-                 IsNew,IsLoopCount: boolean;
+                 IsNew,IsDuplicate,IsLoopCount: boolean;
                end;
                end;
 
 
     FOnValidate: TNotifyEvent;
     FOnValidate: TNotifyEvent;
@@ -63,6 +64,7 @@ type
     procedure ScrollToSelectedMenu;
     procedure ScrollToSelectedMenu;
 
 
     function CanAddNewEntry: boolean;
     function CanAddNewEntry: boolean;
+    function CanDuplicateEntry: boolean;
     function CanDeleteEntry(index: integer): boolean;
     function CanDeleteEntry(index: integer): boolean;
     procedure DeleteEntry(i: integer);
     procedure DeleteEntry(i: integer);
     function GetEntryCount: integer;
     function GetEntryCount: integer;
@@ -70,6 +72,7 @@ type
     function GetEntryHeight(index: integer): integer;
     function GetEntryHeight(index: integer): integer;
     function GetEntryBitDepth(index: integer): integer;
     function GetEntryBitDepth(index: integer): integer;
     function GetEntryBitmap(index: integer): TImageEntry;
     function GetEntryBitmap(index: integer): TImageEntry;
+    procedure SetEntryBitmap(var AEntry: TImageEntry);
     function GetEntryThumbnail(index: integer; stretchWidth, stretchHeight: integer): TBGRABitmap;
     function GetEntryThumbnail(index: integer; stretchWidth, stretchHeight: integer): TBGRABitmap;
 
 
     procedure DrawCurrentFrame(Bitmap: TBGRABitmap);
     procedure DrawCurrentFrame(Bitmap: TBGRABitmap);
@@ -91,6 +94,7 @@ type
     property OnEscape: TNotifyEvent read FOnEscape write FOnEscape;
     property OnEscape: TNotifyEvent read FOnEscape write FOnEscape;
     property EntryCount: integer read GetEntryCount;
     property EntryCount: integer read GetEntryCount;
     function GetPreviewBitmap: TImageEntry;
     function GetPreviewBitmap: TImageEntry;
+    property DuplicateEntrySourceIndex: integer read FDuplicateEntrySourceIndex write FDuplicateEntrySourceIndex;
   end;
   end;
 
 
 implementation
 implementation
@@ -265,7 +269,8 @@ begin
   begin
   begin
     Key := 0;
     Key := 0;
     if (FSelectedMenuIndex >= 0) and (FSelectedMenuIndex <= high(FImageMenu)) and
     if (FSelectedMenuIndex >= 0) and (FSelectedMenuIndex <= high(FImageMenu)) and
-      not FImageMenu[FSelectedMenuIndex].IsNew then
+      not FImageMenu[FSelectedMenuIndex].IsNew and
+      not FImageMenu[FSelectedMenuIndex].IsDuplicate then
     begin
     begin
       DeleteEntry(FImageMenu[FSelectedMenuIndex].FrameIndex);
       DeleteEntry(FImageMenu[FSelectedMenuIndex].FrameIndex);
     end;
     end;
@@ -309,16 +314,31 @@ begin
 end;
 end;
 
 
 procedure TImagePreview.DrawMenu(Bitmap: TBGRABitmap);
 procedure TImagePreview.DrawMenu(Bitmap: TBGRABitmap);
+
+  procedure DrawSheet(x,y,sw,sh: single);
+  var
+    ptsF,ptsF2: ArrayOfTPointF;
+    j: integer;
+  begin
+    ptsF := PointsF([PointF(x+sw*0.20,y+sh*0.1),PointF(x+sw*0.55,y+sh*0.1),PointF(x+sw*0.75,y+sh*0.3),
+                     PointF(x+sw*0.75,y+sh*0.9),PointF(x+sw*0.20,y+sh*0.9)]);
+    setlength(ptsF2,length(ptsF));
+    for j := 0 to high(ptsF) do
+        ptsF2[j] := ptsF[j] + PointF(3,3);
+    bitmap.FillPolyAntialias(ptsF2, BGRA(0,0,0,96));
+    bitmap.FillPolyAntialias(ptsF, BGRAWhite);
+    bitmap.DrawPolygonAntialias(ptsF, BGRABlack, 1.5);
+    bitmap.DrawPolyLineAntialias([PointF(x+sw*0.55,y+sh*0.1),PointF(x+sw*0.55,y+sh*0.3),PointF(x+sw*0.75,y+sh*0.3)], BGRABlack,1.5);
+  end;
+
 var scrollPos, totalHeight, maxScroll, availableWidth: integer;
 var scrollPos, totalHeight, maxScroll, availableWidth: integer;
-  i,j: integer;
+  i: integer;
   x,y,sw,sh: integer;
   x,y,sw,sh: integer;
   textRight, bpp: integer;
   textRight, bpp: integer;
   iconCaption: string;
   iconCaption: string;
-  ptsF,ptsF2: ArrayOfTPointF;
   scrolledArea, inter: TRect;
   scrolledArea, inter: TRect;
 begin
 begin
   if (Bitmap.Width < 8) or (Bitmap.Height < 8) or (GetEntryCount = 0) then exit;
   if (Bitmap.Width < 8) or (Bitmap.Height < 8) or (GetEntryCount = 0) then exit;
-
   if Assigned(FScrollbar) then
   if Assigned(FScrollbar) then
   begin
   begin
     scrollPos := FScrollbar.Position;
     scrollPos := FScrollbar.Position;
@@ -364,10 +384,12 @@ begin
     FSelectedMenuIndex:= -1;
     FSelectedMenuIndex:= -1;
   if (FSelectedMenuIndex = -1) and (length(FImageMenu) > 0) then
   if (FSelectedMenuIndex = -1) and (length(FImageMenu) > 0) then
   begin
   begin
-    if (length(FImageMenu)>=2) and FImageMenu[0].IsNew then
-      FSelectedMenuIndex:= 1 //do not select "add new" entry by default
-    else
-      FSelectedMenuIndex:= 0;
+    FSelectedMenuIndex:= 0;
+    while (FSelectedMenuIndex < length(FImageMenu)) and
+      (FImageMenu[FSelectedMenuIndex].IsNew or FImageMenu[FSelectedMenuIndex].IsDuplicate
+       or FImageMenu[FSelectedMenuIndex].IsLoopCount) do
+      inc(FSelectedMenuIndex);
+    //do not select special entries by default
   end;
   end;
 
 
   for i := 0 to high(FImageMenu) do
   for i := 0 to high(FImageMenu) do
@@ -382,7 +404,7 @@ begin
     if i = FSelectedMenuIndex then
     if i = FSelectedMenuIndex then
     begin
     begin
       bitmap.FillRect(scrolledArea, ColorToRGB(clHighlight));
       bitmap.FillRect(scrolledArea, ColorToRGB(clHighlight));
-      if not IsNew and not IsLoopCount and (Area.Right - IconArea.Right > 32) and CanDeleteEntry(FrameIndex) then
+      if not IsNew and not IsLoopCount and not IsDuplicate and (Area.Right - IconArea.Right > 32) and CanDeleteEntry(FrameIndex) then
       begin
       begin
         sh := (Area.Right - IconArea.Right - 8) div 4;
         sh := (Area.Right - IconArea.Right - 8) div 4;
         if sh < 16 then sh := 16;
         if sh < 16 then sh := 16;
@@ -416,15 +438,13 @@ begin
     end else
     end else
     if IsNew then
     if IsNew then
     begin
     begin
-      ptsF := PointsF([PointF(x+sw*0.20,y+sh*0.1),PointF(x+sw*0.55,y+sh*0.1),PointF(x+sw*0.75,y+sh*0.3),
-                       PointF(x+sw*0.75,y+sh*0.9),PointF(x+sw*0.20,y+sh*0.9)]);
-      setlength(ptsF2,length(ptsF));
-      for j := 0 to high(ptsF) do
-          ptsF2[j] := ptsF[j] + PointF(3,3);
-      bitmap.FillPolyAntialias(ptsF2, BGRA(0,0,0,96));
-      bitmap.FillPolyAntialias(ptsF, BGRAWhite);
-      bitmap.DrawPolygonAntialias(ptsF, BGRABlack, 1.5);
-      bitmap.DrawPolyLineAntialias([PointF(x+sw*0.55,y+sh*0.1),PointF(x+sw*0.55,y+sh*0.3),PointF(x+sw*0.75,y+sh*0.3)], BGRABlack,1.5);
+      DrawSheet(x,y,sw,sh);
+    end else
+    if IsDuplicate then
+    begin
+      DrawSheet(x-sw*0.15,y-sh*0.1,sw,sh*0.9);
+      DrawSheet(x+sw*0.1,y+sh*0.1,sw,sh*0.9);
+      bitmap.FontFullHeight:= round(sh*0.7);
     end else
     end else
     begin
     begin
       bitmap.FillRect(rect(x+2,y+2, x+sw+2,y+sh+2), BGRA(0,0,0,96), dmDrawWithTransparency);
       bitmap.FillRect(rect(x+2,y+2, x+sw+2,y+sh+2), BGRA(0,0,0,96), dmDrawWithTransparency);
@@ -432,6 +452,7 @@ begin
     end;
     end;
 
 
     if IsNew then iconCaption := rsNewImage else
     if IsNew then iconCaption := rsNewImage else
+    if IsDuplicate then iconCaption := rsDuplicateImage else
     if IsLoopCount then
     if IsLoopCount then
     begin
     begin
       iconCaption:= rsLoopCount+': ';
       iconCaption:= rsLoopCount+': ';
@@ -466,7 +487,8 @@ end;
 
 
 function TImagePreview.TryMenuLayout(AWidth: integer; AColCount, ABottom: integer): integer;
 function TImagePreview.TryMenuLayout(AWidth: integer; AColCount, ABottom: integer): integer;
 var x,y,i,frameIndex,h,w,sw,sh: integer;
 var x,y,i,frameIndex,h,w,sw,sh: integer;
-  newItem, LoopCountItem, colLeft,colRight, maxWidth, maxHeight: integer;
+  newItem, LoopCountItem, DuplicateItem,
+  colLeft,colRight, maxWidth, maxHeight: integer;
   currentCol: integer;
   currentCol: integer;
 
 
   procedure ComputeColumn;
   procedure ComputeColumn;
@@ -490,8 +512,9 @@ begin
 
 
   if Assigned(FAnimatedGif) then LoopCountItem := 1 else LoopCountItem:= 0;
   if Assigned(FAnimatedGif) then LoopCountItem := 1 else LoopCountItem:= 0;
   if CanAddNewEntry then NewItem := 1 else NewItem := 0;
   if CanAddNewEntry then NewItem := 1 else NewItem := 0;
-  setlength(FImageMenu, GetEntryCount + NewItem + LoopCountItem);
-  for i := 0 to GetEntryCount-1 + NewItem + LoopCountItem do
+  if CanDuplicateEntry then DuplicateItem := 1 else DuplicateItem := 0;
+  setlength(FImageMenu, GetEntryCount + LoopCountItem + NewItem + DuplicateItem);
+  for i := 0 to high(FImageMenu) do
   begin
   begin
     if (LoopCountItem = 1) and (i = 0) then
     if (LoopCountItem = 1) and (i = 0) then
     begin
     begin
@@ -508,8 +531,16 @@ begin
       h := 32;
       h := 32;
     end
     end
     else
     else
+    if (DuplicateItem = 1) and (i = LoopCountItem + NewItem) then
     begin
     begin
-      frameIndex := i-NewItem-LoopCountItem;
+      frameIndex := GetEntryCount;
+      FImageMenu[i].IsDuplicate := true;
+      w := 32;
+      h := 32;
+    end
+    else
+    begin
+      frameIndex := i-NewItem-LoopCountItem-DuplicateItem;
       w := GetEntryWidth(frameIndex);
       w := GetEntryWidth(frameIndex);
       h := GetEntryHeight(frameIndex);
       h := GetEntryHeight(frameIndex);
     end;
     end;
@@ -549,6 +580,12 @@ begin
   result := Assigned(FIconCursor) or Assigned(FTiff) or Assigned(FAnimatedGif);
   result := Assigned(FIconCursor) or Assigned(FTiff) or Assigned(FAnimatedGif);
 end;
 end;
 
 
+function TImagePreview.CanDuplicateEntry: boolean;
+begin
+  result := (Assigned(FTiff) or Assigned(FAnimatedGif)) and
+    (FDuplicateEntrySourceIndex >= 0) and (FDuplicateEntrySourceIndex < EntryCount);
+end;
+
 function TImagePreview.GetEntryCount: integer;
 function TImagePreview.GetEntryCount: integer;
 begin
 begin
   if Assigned(FIconCursor) then
   if Assigned(FIconCursor) then
@@ -641,6 +678,56 @@ begin
   end;
   end;
 end;
 end;
 
 
+procedure TImagePreview.SetEntryBitmap(var AEntry: TImageEntry);
+var
+  sAddedTiff: TMemoryStream;
+  addedTiff: TTiff;
+  sOut: TStream;
+begin
+  if (AEntry.frameIndex < 0) or (AEntry.frameIndex > GetEntryCount) then
+    raise exception.Create('Index out of bounds');
+  if Filename = '' then raise exception.create('Filename undefined');
+
+  if Assigned(FTiff) then
+  begin
+    addedTiff := TTiff.Create;
+    sAddedTiff := TMemoryStream.Create;
+    try
+      AEntry.bmp.SaveToStreamAs(sAddedTiff, ifTiff);
+      sAddedTiff.Position:= 0;
+      if addedTiff.LoadFromStream(sAddedTiff) <> teNone then
+        raise Exception.Create(rsInternalError);
+      if AEntry.frameIndex > FTiff.Count then
+        AEntry.frameIndex := FTiff.Count;
+      FTiff.Move(addedTiff,0, AEntry.frameIndex);
+
+      sOut := FileManager.CreateFileStream(Filename,fmCreate);
+      try
+        FTiff.SaveToStream(sOut);
+      finally
+        sOut.Free;
+      end;
+    finally
+      sAddedTiff.Free;
+      addedTiff.Free;
+    end;
+  end else
+  if Assigned(FAnimatedGif) then
+  begin
+    if AEntry.frameIndex >= FAnimatedGif.Count then
+      AEntry.frameIndex := FAnimatedGif.AddFullFrame(AEntry.bmp, FAnimatedGif.AverageDelayMs)
+    else
+      FAnimatedGif.ReplaceFullFrame(AEntry.frameIndex, AEntry.bmp, FAnimatedGif.FrameDelayMs[AEntry.frameIndex]);
+
+    sOut := FileManager.CreateFileStream(Filename,fmCreate);
+    try
+      FAnimatedGif.SaveToStream(sOut);
+    finally
+      sOut.Free;
+    end;
+  end;
+end;
+
 function TImagePreview.GetEntryThumbnail(index: integer; stretchWidth, stretchHeight: integer): TBGRABitmap;
 function TImagePreview.GetEntryThumbnail(index: integer; stretchWidth, stretchHeight: integer): TBGRABitmap;
 var
 var
   entry: TImageEntry;
   entry: TImageEntry;
@@ -980,6 +1067,7 @@ begin
   FStatus := AStatus;
   FStatus := AStatus;
   FAnimate:= AAnimate;
   FAnimate:= AAnimate;
   FSelectedMenuIndex := -1;
   FSelectedMenuIndex := -1;
+  FDuplicateEntrySourceIndex := -1;
   {$IFDEF WINDOWS}
   {$IFDEF WINDOWS}
   ASurface.Color := clAppWorkspace;
   ASurface.Color := clAppWorkspace;
   {$ENDIF}
   {$ENDIF}
@@ -1095,6 +1183,14 @@ begin
             result.frameIndex:= TImageEntry.NewFrameIndex;
             result.frameIndex:= TImageEntry.NewFrameIndex;
           end;
           end;
         end else
         end else
+        if FImageMenu[FSelectedMenuIndex].IsDuplicate then
+        begin
+          result := GetEntryBitmap(DuplicateEntrySourceIndex);
+          result.frameIndex:= GetEntryCount;
+          result.isDuplicate:= true;
+          SetEntryBitmap(result);
+        end
+        else
           result := GetEntryBitmap(FImageMenu[FSelectedMenuIndex].FrameIndex);
           result := GetEntryBitmap(FImageMenu[FSelectedMenuIndex].FrameIndex);
       end;
       end;
     end else
     end else
@@ -1122,6 +1218,14 @@ begin
               result.bmp := TBGRABitmap.Create(FAnimatedGif.Width,FAnimatedGif.Height,BGRAPixelTransparent);
               result.bmp := TBGRABitmap.Create(FAnimatedGif.Width,FAnimatedGif.Height,BGRAPixelTransparent);
               result.frameIndex:= TImageEntry.NewFrameIndex;
               result.frameIndex:= TImageEntry.NewFrameIndex;
           end else
           end else
+          if FImageMenu[FSelectedMenuIndex].IsDuplicate then
+          begin
+            result := GetEntryBitmap(DuplicateEntrySourceIndex);
+            result.frameIndex:= GetEntryCount;
+            result.isDuplicate:= true;
+            SetEntryBitmap(result);
+          end
+          else
             result := GetEntryBitmap(FImageMenu[FSelectedMenuIndex].FrameIndex);
             result := GetEntryBitmap(FImageMenu[FSelectedMenuIndex].FrameIndex);
         end;
         end;
       end else
       end else

+ 1 - 0
lazpaint/uresourcestrings.pas

@@ -63,6 +63,7 @@ resourcestring
   rsMergeSelection='Do you want to merge selection?';
   rsMergeSelection='Do you want to merge selection?';
   rsSave='Save';
   rsSave='Save';
   rsNewImage='New image';
   rsNewImage='New image';
+  rsDuplicateImage='Duplicate image';
   rsOpen='Open';
   rsOpen='Open';
   rsReload='Reload';
   rsReload='Reload';
   rsReloadChanged='Bitmap has been modified. Do you really want to reload?';
   rsReloadChanged='Bitmap has been modified. Do you really want to reload?';

+ 22 - 0
lazpaint/uscripting.pas

@@ -51,6 +51,7 @@ type
     function GetSubsetByName(const AName: string): TVariableSet;
     function GetSubsetByName(const AName: string): TVariableSet;
     function GetListByName(const AName: string): string;
     function GetListByName(const AName: string): string;
     function GetVariablesAsString: string;
     function GetVariablesAsString: string;
+    function GetVarName(AIndex: integer): string;
     procedure SetBooleanByName(const AName: string; AValue: boolean);
     procedure SetBooleanByName(const AName: string; AValue: boolean);
     procedure SetFloatByName(const AName: string; AValue: double);
     procedure SetFloatByName(const AName: string; AValue: double);
     procedure SetIntegerByName(const AName: string; AValue: TScriptInteger);
     procedure SetIntegerByName(const AName: string; AValue: TScriptInteger);
@@ -126,6 +127,7 @@ type
     function CopyValuesTo(ASet: TVariableSet): boolean;
     function CopyValuesTo(ASet: TVariableSet): boolean;
     property FunctionName: string read FFunctionName;
     property FunctionName: string read FFunctionName;
     property Count: NativeInt read GetCount;
     property Count: NativeInt read GetCount;
+    property VariableName[AIndex: integer]: string read GetVarName;
     property VariablesAsString: string read GetVariablesAsString;
     property VariablesAsString: string read GetVariablesAsString;
     property Floats[const AName: string]: double read GetFloatByName write SetFloatByName;
     property Floats[const AName: string]: double read GetFloatByName write SetFloatByName;
     property Integers[const AName: string]: TScriptInteger read GetIntegerByName write SetIntegerByName;
     property Integers[const AName: string]: TScriptInteger read GetIntegerByName write SetIntegerByName;
@@ -386,6 +388,26 @@ begin
 
 
 end;
 end;
 
 
+function TVariableSet.GetVarName(AIndex: integer): string;
+begin
+  if AIndex < 0 then raise exception.Create('Index out of bounds');
+
+  if AIndex < FNbScalars then exit(FScalars[AIndex].name)
+  else dec(AIndex, FNbScalars);
+  if AIndex < FNbStrings then exit(FStrings[AIndex].name)
+  else dec(AIndex, FNbStrings);
+  if AIndex < FNbBoolLists then exit(FBoolLists[AIndex].name)
+  else dec(AIndex, FNbBoolLists);
+  if AIndex < FNbScalarLists then exit(FScalarLists[AIndex].name)
+  else dec(AIndex, FNbScalarLists);
+  if AIndex < FNbStrLists then exit(FStrLists[AIndex].name)
+  else dec(AIndex, FNbStrLists);
+  if AIndex < FNbSubsets then exit(FSubsets[AIndex].name)
+  else dec(AIndex, FNbSubsets);
+
+  raise exception.Create('Index out of bounds');
+end;
+
 function TVariableSet.LoadFromVariablesAsString(AVariablesAsString: string
 function TVariableSet.LoadFromVariablesAsString(AVariablesAsString: string
   ): TInterpretationErrors;
   ): TInterpretationErrors;
 var varName: string;
 var varName: string;