Răsfoiți Sursa

Merge pull request #29 from bgrabitmap/dev-vectorial

Dev vectorial
circular17 6 ani în urmă
părinte
comite
87223048bf
57 a modificat fișierele cu 3883 adăugiri și 1910 ștergeri
  1. 15 7
      lazpaint/lazpaint.lpi
  2. 1 1
      lazpaint/lazpaintinstance.pas
  3. 22 3
      lazpaint/lazpaintmainform.lfm
  4. 104 66
      lazpaint/lazpaintmainform.pas
  5. 2 2
      lazpaint/lazpainttype.pas
  6. 1 0
      lazpaint/maintoolbar.inc
  7. 29 0
      lazpaint/release/i18n/lazpaint.ar.po
  8. 29 0
      lazpaint/release/i18n/lazpaint.cs.po
  9. 29 0
      lazpaint/release/i18n/lazpaint.de.po
  10. 29 0
      lazpaint/release/i18n/lazpaint.es.po
  11. 29 0
      lazpaint/release/i18n/lazpaint.fi.po
  12. 29 0
      lazpaint/release/i18n/lazpaint.fr.po
  13. 29 0
      lazpaint/release/i18n/lazpaint.ja.po
  14. 29 0
      lazpaint/release/i18n/lazpaint.lv.po
  15. 29 0
      lazpaint/release/i18n/lazpaint.nl.po
  16. 29 0
      lazpaint/release/i18n/lazpaint.po
  17. 29 0
      lazpaint/release/i18n/lazpaint.pt_BR.po
  18. 29 0
      lazpaint/release/i18n/lazpaint.ru.po
  19. 29 0
      lazpaint/release/i18n/lazpaint.sv.po
  20. 1 1
      lazpaint/uadjustcurves.pas
  21. 25 11
      lazpaint/ucanvassize.pas
  22. 1 1
      lazpaint/ucolorintensity.pas
  23. 1 1
      lazpaint/ucolorize.pas
  24. 2 2
      lazpaint/ucommandline.pas
  25. 3 3
      lazpaint/uconfig.pas
  26. 3 1
      lazpaint/ufileextensions.pas
  27. 33 13
      lazpaint/ufilterconnector.pas
  28. 10 2
      lazpaint/ufilters.pas
  29. 26 14
      lazpaint/ugraph.pas
  30. 294 323
      lazpaint/uimage.pas
  31. 235 136
      lazpaint/uimageaction.pas
  32. 584 93
      lazpaint/uimagediff.pas
  33. 148 133
      lazpaint/uimagepreview.pas
  34. 565 142
      lazpaint/uimagestate.pas
  35. 5 3
      lazpaint/uimageview.pas
  36. 399 183
      lazpaint/ulayeraction.pas
  37. 106 29
      lazpaint/ulayerstack.pas
  38. 32 2
      lazpaint/uloadimage.pas
  39. 1 1
      lazpaint/umenu.pas
  40. 2 2
      lazpaint/upalettetoolbar.pas
  41. 1 1
      lazpaint/uposterize.pas
  42. 1 0
      lazpaint/uresourcestrings.pas
  43. 35 35
      lazpaint/uscripting.pas
  44. 2 2
      lazpaint/uselectionhighlight.pas
  45. 1 1
      lazpaint/ushiftcolors.pas
  46. 474 300
      lazpaint/ustatetype.pas
  47. 109 82
      lazpaint/utool.pas
  48. 39 67
      lazpaint/utoolbasic.pas
  49. 1 2
      lazpaint/utoolbrush.pas
  50. 30 31
      lazpaint/utooldeformationgrid.pas
  51. 1 1
      lazpaint/utoolfloodfill.pas
  52. 151 78
      lazpaint/utoollayer.pas
  53. 1 4
      lazpaint/utoolphong.pas
  54. 4 20
      lazpaint/utoolpolygon.pas
  55. 24 75
      lazpaint/utoolselect.pas
  56. 2 11
      lazpaint/utooltext.pas
  57. 9 25
      lazpaintcontrols/lctoolbars.pas

+ 15 - 7
lazpaint/lazpaint.lpi

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <CONFIG>
   <ProjectOptions>
-    <Version Value="10"/>
+    <Version Value="9"/>
     <PathDelim Value="\"/>
     <General>
       <SessionStorage Value="InProjectDir"/>
@@ -23,7 +23,7 @@
       <MinorVersionNr Value="4"/>
       <RevisionNr Value="1"/>
       <CharSet Value="04B0"/>
-      <StringTable CompanyName="http://sourceforge.net/projects/lazpaint/" ProductName="LazPaint" InternalName="lazpaint" OriginalFilename="lazpaint.exe"/>
+      <StringTable CompanyName="http://sourceforge.net/projects/lazpaint/" ProductName="LazPaint" InternalName="lazpaint" OriginalFilename="lazpaint.exe" ProductVersion=""/>
     </VersionInfo>
     <BuildModes Count="4">
       <Item1 Name="Debug" Default="True"/>
@@ -157,19 +157,22 @@
         <LaunchingApplication PathPlusParams="\usr\X11R6\bin\xterm -T 'Lazarus Run Output' -e $(LazarusDir)\tools\runwait.sh $(TargetCmdLine)"/>
       </local>
     </RunParams>
-    <RequiredPackages Count="4">
+    <RequiredPackages Count="5">
       <Item1>
-        <PackageName Value="BGRABitmapPack"/>
+        <PackageName Value="lazpaintcontrols"/>
       </Item1>
       <Item2>
-        <PackageName Value="Printer4Lazarus"/>
+        <PackageName Value="BGRABitmapPack"/>
       </Item2>
       <Item3>
-        <PackageName Value="bgracontrols"/>
+        <PackageName Value="Printer4Lazarus"/>
       </Item3>
       <Item4>
-        <PackageName Value="LCL"/>
+        <PackageName Value="bgracontrols"/>
       </Item4>
+      <Item5>
+        <PackageName Value="LCL"/>
+      </Item5>
     </RequiredPackages>
     <Units Count="97">
       <Unit0>
@@ -404,6 +407,7 @@
       <Unit36>
         <Filename Value="utoolphong.pas"/>
         <IsPartOfProject Value="True"/>
+        <UnitName Value="UToolPhong"/>
       </Unit36>
       <Unit37>
         <Filename Value="utooltext.pas"/>
@@ -460,10 +464,12 @@
         <ComponentName Value="FLoading"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
+        <UnitName Value="ULoading"/>
       </Unit45>
       <Unit46>
         <Filename Value="ufilterconnector.pas"/>
         <IsPartOfProject Value="True"/>
+        <UnitName Value="UFilterConnector"/>
       </Unit46>
       <Unit47>
         <Filename Value="uzoom.pas"/>
@@ -567,6 +573,7 @@
       <Unit65>
         <Filename Value="uimagetype.pas"/>
         <IsPartOfProject Value="True"/>
+        <UnitName Value="UImageType"/>
       </Unit65>
       <Unit66>
         <Filename Value="uposterize.pas"/>
@@ -725,6 +732,7 @@
         <Filename Value="uquestion.pas"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="FQuestion"/>
+        <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <UnitName Value="UQuestion"/>
       </Unit94>

+ 1 - 1
lazpaint/lazpaintinstance.pas

@@ -1299,7 +1299,7 @@ begin
     FLayerStack.SetLayerStackScrollPosOnItem(AIndex);
     if FMain <> nil then
     begin
-      FMain.StackNeedUpdate := true;
+      FMain.UpdateStackOnTimer := true;
     end else
       NotifyStackChange;
   end;

+ 22 - 3
lazpaint/lazpaintmainform.lfm

@@ -1,7 +1,7 @@
 object FMain: TFMain
-  Left = 467
+  Left = 462
   Height = 578
-  Top = 93
+  Top = 16
   Width = 739
   AllowDropFiles = True
   Caption = 'LazPaint'
@@ -31,7 +31,7 @@ object FMain: TFMain
   OnUTF8KeyPress = FormUTF8KeyPress
   Position = poDefault
   ShowHint = True
-  LCLVersion = '1.8.0.6'
+  LCLVersion = '1.6.0.4'
   object Panel_ToolbarBackground: TPanel
     Left = 8
     Height = 36
@@ -5504,6 +5504,7 @@ object FMain: TFMain
     }
   end
   object ActionList1: TActionList
+    Images = ImageList16
     left = 200
     top = 432
     object FileNew: TAction
@@ -6092,6 +6093,7 @@ object FMain: TFMain
     object ImageFlatten: TAction
       Category = 'Image'
       Caption = 'Flatten image'
+      ImageIndex = 83
       OnExecute = ImageFlattenExecute
       OnUpdate = ImageFlattenUpdate
       ShortCut = 24646
@@ -6420,6 +6422,23 @@ object FMain: TFMain
       Hint = 'Forget answers given to dialog boxes'
       OnExecute = ForgetDialogAnswersExecute
     end
+    object ImageNegative: TAction
+      Category = 'Image'
+      Caption = 'Negative'
+      ImageIndex = 66
+      OnExecute = ScriptExecute
+    end
+    object ImageLinearNegative: TAction
+      Category = 'Image'
+      Caption = 'Linear negative'
+      ImageIndex = 66
+      OnExecute = ScriptExecute
+    end
+    object ImageSwapRedBlue: TAction
+      Category = 'Image'
+      Caption = 'Swap red and blue channels'
+      OnExecute = ScriptExecute
+    end
   end
   object ColorDialog1: TColorDialog
     Title = 'Choose color'

+ 104 - 66
lazpaint/lazpaintmainform.pas

@@ -13,7 +13,7 @@ uses
   Controls, Graphics, Dialogs, Menus, ExtDlgs, ComCtrls, ActnList, StdCtrls,
   ExtCtrls, Buttons, types, LCLType, BGRAImageList, BGRAVirtualScreen,
 
-  BGRABitmap, BGRABitmapTypes, BGRALayers,
+  BGRABitmap, BGRABitmapTypes, BGRALayers, BGRASVGOriginal,
 
   LazPaintType, UMainFormLayout, UTool, UImage, UImageAction, ULayerAction, UZoom, UImageView,
   UImageObservation, UConfig, UScaleDPI, UResourceStrings,
@@ -28,6 +28,9 @@ type
   { TFMain }
 
   TFMain = class(TForm)
+    ImageSwapRedBlue: TAction;
+    ImageLinearNegative: TAction;
+    ImageNegative: TAction;
     ForgetDialogAnswers: TAction;
     FileChooseEntry: TAction;
     ToolButton8: TToolButton;
@@ -711,6 +714,7 @@ type
     FCoordinatesCaption: string;
     FCoordinatesCaptionCount: NativeInt;
     FImageView: TImageView;
+    FUpdateStackWhenIdle: boolean;
 
     function GetCurrentPressure: single;
     function GetUseImageBrowser: boolean;
@@ -792,7 +796,7 @@ type
   public
     { public declarations }
     FormBackgroundColor: TColor;
-    StackNeedUpdate: boolean;
+    UpdateStackOnTimer: boolean;
     Zoom: TZoom;
 
     procedure PaintPictureNow;
@@ -803,6 +807,7 @@ type
     procedure UpdateToolbar;
     function ChooseTool(Tool : TPaintToolType): boolean;
     procedure PictureSelectedLayerIndexChanged({%H-}sender: TLazPaintImage);
+    procedure PictureSelectedLayerIndexChanging({%H-}sender: TLazPaintImage);
     property LazPaintInstance: TLazPaintCustomInstance read FLazPaintInstance write SetLazPaintInstance;
     procedure UpdateEditPicture(ADelayed: boolean = false);
     property CurrentTool: TPaintToolType read GetCurrentTool;
@@ -818,7 +823,7 @@ implementation
 
 uses LCLIntf, BGRAUTF8, ugraph, math, umac, uclipboard, ucursors,
    ufilters, ULoadImage, ULoading, UFileExtensions, UBrushType,
-   ugeometricbrush, UPreviewDialog, UQuestion;
+   ugeometricbrush, UPreviewDialog, UQuestion, BGRALayerOriginal;
 
 const PenWidthFactor = 10;
 
@@ -922,6 +927,7 @@ begin
   begin
     Image.OnSelectionChanged := nil;
     Image.OnSelectedLayerIndexChanged:= nil;
+    Image.OnSelectedLayerIndexChanging:= nil;
   end;
   FLayout.ToolBoxPopup := nil;
   RegisterScripts(False);
@@ -977,6 +983,7 @@ begin
   LazPaintInstance.EmbeddedResult := mrNone;
 
   Image.OnSelectedLayerIndexChanged:= @PictureSelectedLayerIndexChanged;
+  Image.OnSelectedLayerIndexChanging:= @PictureSelectedLayerIndexChanging;
   Image.CurrentFilenameUTF8 := '';
 
   RegisterToolbarElements;
@@ -1160,6 +1167,11 @@ begin
     btnMiddleDown:= false;
   end;
   if redraw then PaintPictureNow;
+  if FUpdateStackWhenIdle then
+  begin
+    UpdateStackOnTimer:= true;
+    FUpdateStackWhenIdle:= false;
+  end;
   UpdateToolbar;
   ReleaseMouseButtons(Shift);
 
@@ -1253,7 +1265,7 @@ begin
         begin
           if length(chosenFiles) = 1 then
           begin
-            if TryOpenFileUTF8(chosenFiles[0],true,@loadedImage) then
+            if TryOpenFileUTF8(chosenFiles[0],true,@loadedImage,true) then
             begin
               result := srOk;
               if Assigned(Scripting.RecordingFunctionParameters) then
@@ -1578,8 +1590,8 @@ begin
   if Sender is TAction then
   begin
     actionName:= (Sender as TAction).Name;
-    if (actionName = 'ImageHorizontalFlip') and not image.SelectionEmpty then actionName := 'SelectionHorizontalFlip' else
-    if (actionName = 'ImageVerticalFlip') and not image.SelectionEmpty  then actionName := 'SelectionVerticalFlip';
+    if (actionName = 'ImageHorizontalFlip') and not image.SelectionMaskEmpty then actionName := 'SelectionHorizontalFlip' else
+    if (actionName = 'ImageVerticalFlip') and not image.SelectionMaskEmpty  then actionName := 'SelectionVerticalFlip';
     CallScriptFunction(actionName);
   end;
 end;
@@ -1713,7 +1725,7 @@ function TFMain.ScriptFileSaveSelectionAs(AVars: TVariableSet): TScriptResult;
 var filename: string;
     vFileName: TScriptVariableReference;
 begin
-  if Image.SelectionEmpty then
+  if Image.SelectionMaskEmpty then
   begin
     result := srOk;
     exit;
@@ -1762,7 +1774,7 @@ begin
     end else
     begin
       try
-        Image.SaveSelectionToFileUTF8(filename);
+        Image.SaveSelectionMaskToFileUTF8(filename);
         result := srOk;
         if Assigned(Scripting.RecordingFunctionParameters) then
            Scripting.RecordingFunctionParameters.AddString('FileName',filename);
@@ -1795,7 +1807,7 @@ end;
 
 procedure TFMain.FileSaveSelectionAsUpdate(Sender: TObject);
 begin
-  FileSaveSelectionAs.Enabled := not Image.SelectionEmpty;
+  FileSaveSelectionAs.Enabled := not Image.SelectionMaskEmpty;
 end;
 
 procedure TFMain.FormDropFiles(Sender: TObject; const FileNames: array of String);
@@ -1804,10 +1816,12 @@ var
   Errors: String='';
   loadedLayers: array of record
      bmp: TBGRABitmap;
+     orig: TBGRALayerCustomOriginal;
      filename: string;
   end;
   topmost: TTopMostInfo;
   choice: TModalResult;
+  svgOrig: TBGRALayerSVGOriginal;
 begin
   if Length(FileNames)<1 then exit;
   if Length(FileNames)= 1
@@ -1838,9 +1852,21 @@ begin
                   MessagePopupForever(rsLoading + ' ' + inttostr(i+1) + '/' + inttostr(length(FileNames)));
                   LazPaintInstance.UpdateWindows;
                   loadedLayers[i].filename := Filenames[i];
-                  loadedLayers[i].bmp := LoadFlatImageUTF8(Filenames[i]).bmp;
-                  if loadedLayers[i].bmp.Width > tx then tx := loadedLayers[i].bmp.Width;
-                  if loadedLayers[i].bmp.Height > ty then ty := loadedLayers[i].bmp.Height;
+                  case DetectFileFormat(Filenames[i]) of
+                   ifSvg:
+                     begin
+                       svgOrig := LoadSVGOriginalUTF8(Filenames[i]);
+                       loadedLayers[i].orig := svgOrig;
+                       if ceil(svgOrig.Width) > tx then tx := ceil(svgOrig.Width);
+                       if ceil(svgOrig.Height) > ty then ty := ceil(svgOrig.Height);
+                     end
+                   else
+                     begin
+                       loadedLayers[i].bmp := LoadFlatImageUTF8(Filenames[i]).bmp;
+                       if loadedLayers[i].bmp.Width > tx then tx := loadedLayers[i].bmp.Width;
+                       if loadedLayers[i].bmp.Height > ty then ty := loadedLayers[i].bmp.Height;
+                     end;
+                  end;
                   MessagePopupHide;
                 except on ex:exception do
                   //begin
@@ -1857,9 +1883,14 @@ begin
                   Image.Assign(TBGRABitmap.Create(tx,ty),true,false);
                   ZoomFitIfTooBig;
                   for i := 0 to high(loadedLayers) do
+                  if Assigned(loadedLayers[i].bmp) then
                   begin
                     FImageActions.AddLayerFromBitmap(loadedLayers[i].bmp,ExtractFileName(loadedLayers[i].filename));
                     loadedLayers[i].bmp := nil;
+                  end else
+                  begin
+                    FImageActions.AddLayerFromOriginal(loadedLayers[i].orig,ExtractFileName(loadedLayers[i].filename));
+                    loadedLayers[i].orig := nil;
                   end;
                 end;
               except on ex:exception do
@@ -1878,7 +1909,10 @@ begin
                 LazPaintInstance.ShowTopmost(topmost);
               end;
               for i := 0 to high(loadedLayers) do
+              begin
                 FreeAndNil(loadedLayers[i].bmp);
+                FreeAndNil(loadedLayers[i].orig);
+              end;
           end;  //OpenFilesAsLayers
        mrLast+2: begin
              if not LazPaintInstance.ImageListWindowVisible then
@@ -1986,7 +2020,7 @@ end;
 
 procedure TFMain.ImageCropLayerUpdate(Sender: TObject);
 begin
-  ImageCropLayer.Enabled := not image.SelectionEmpty;
+  ImageCropLayer.Enabled := not image.SelectionMaskEmpty;
   ImageCropLayer.Visible := (image.NbLayers > 1);
 end;
 
@@ -2086,7 +2120,7 @@ end;
 
 procedure TFMain.LayerMergeOverUpdate(Sender: TObject);
 begin
-  LayerMergeOver.Enabled := (image.currentImageLayerIndex > 0) and Image.CurrentLayerVisible;
+  LayerMergeOver.Enabled := (image.CurrentLayerIndex > 0) and Image.CurrentLayerVisible;
 end;
 
 procedure TFMain.LayerMoveExecute(Sender: TObject);
@@ -2096,7 +2130,7 @@ end;
 
 procedure TFMain.LayerMoveUpdate(Sender: TObject);
 begin
-  LayerMove.Enabled := Image.CurrentLayerVisible and Image.SelectionEmpty;
+  LayerMove.Enabled := Image.CurrentLayerVisible and Image.SelectionMaskEmpty;
 end;
 
 procedure TFMain.LayerRemoveCurrentUpdate(Sender: TObject);
@@ -2111,7 +2145,7 @@ end;
 
 procedure TFMain.LayerRotateUpdate(Sender: TObject);
 begin
-  LayerRotate.Enabled := Image.CurrentLayerVisible and Image.SelectionEmpty;
+  LayerRotate.Enabled := Image.CurrentLayerVisible and Image.SelectionMaskEmpty;
 end;
 
 procedure TFMain.ItemDonateClick(Sender: TObject);
@@ -2137,11 +2171,11 @@ end;
 procedure TFMain.MenuImageClick(Sender: TObject);
 begin
   ItemHorizFlipLayer.Visible := (image.NbLayers > 1) and not LazPaintInstance.LayerWindowVisible;
-  ItemHorizFlipSelection.Visible := not image.SelectionEmpty;
+  ItemHorizFlipSelection.Visible := not image.SelectionMaskEmpty;
   ImageHorizontalFlip.Visible := not ItemHorizFlipLayer.Visible and not ItemHorizFlipSelection.Visible;
   MenuHorizFlipSub.Visible := not ImageHorizontalFlip.Visible;
   ItemVertFlipLayer.Visible := (image.NbLayers > 1) and not LazPaintInstance.LayerWindowVisible;
-  ItemVertFlipSelection.Visible := not image.SelectionEmpty;
+  ItemVertFlipSelection.Visible := not image.SelectionMaskEmpty;
   ImageVerticalFlip.Visible := not ItemVertFlipLayer.Visible and not ItemVertFlipSelection.Visible;
   MenuVertFlipSub.Visible := not ImageVerticalFlip.Visible;
 end;
@@ -2279,7 +2313,7 @@ end;
 
 procedure TFMain.ImageCropUpdate(Sender: TObject);
 begin
-  ImageCrop.Enabled := not image.SelectionEmpty;
+  ImageCrop.Enabled := not image.SelectionMaskEmpty;
 end;
 
 procedure TFMain.ImageRepeatExecute(Sender: TObject);
@@ -2367,10 +2401,10 @@ begin
     Label_Coordinates.Update;
     FCoordinatesCaptionCount := 0;
   end;
-  if CanCompressOrUpdateStack and StackNeedUpdate then
+  if CanCompressOrUpdateStack and UpdateStackOnTimer then
   begin
     LazPaintInstance.NotifyStackChange;
-    StackNeedUpdate := false;
+    UpdateStackOnTimer := false;
   end else
   begin
     if CanCompressOrUpdateStack then image.CompressUndo;
@@ -2385,12 +2419,12 @@ end;
 
 procedure TFMain.ToolRotateSelectionUpdate(Sender: TObject);
 begin
-  ToolRotateSelection.Enabled := not image.SelectionEmpty;
+  ToolRotateSelection.Enabled := not image.SelectionMaskEmpty;
 end;
 
 procedure TFMain.ToolLayerMappingUpdate(Sender: TObject);
 begin
-  ToolLayerMapping.Enabled := Image.CurrentLayerVisible and Image.SelectionEmpty;
+  ToolLayerMapping.Enabled := Image.CurrentLayerVisible and Image.SelectionMaskEmpty;
 end;
 
 procedure TFMain.ToolLoadTextureExecute(Sender: TObject);
@@ -2622,7 +2656,7 @@ begin
           begin
             useSelection:= false;
             newTexture := nil;
-            if not image.SelectionEmpty and not image.SelectionLayerIsEmpty then
+            if not image.SelectionMaskEmpty and not image.SelectionLayerIsEmpty then
             begin
               topmostInfo := LazPaintInstance.HideTopmost;
               if Config.DefaultTransformSelectionAnswer <> mrNone then
@@ -2640,19 +2674,15 @@ begin
                   if image.SelectionLayerReadonly <> nil then
                   begin
                     newTexture := image.SelectionLayerReadonly.Duplicate as TBGRABitmap;
-                    newTexture.ApplyMask(image.SelectionReadonly, image.SelectionLayerBounds);
+                    newTexture.ApplyMask(image.SelectionMaskReadonly, image.SelectionLayerBounds);
                     if newTexture.Empty then
-                      MessagePopup(rsNothingToBeRetrieved,2000)
+                    begin
+                      newTexture.Free;
+                      MessagePopup(rsNothingToBeRetrieved,2000);
+                    end
                     else
                     begin
-                      LayerAction := nil;
-                      try
-                        LayerAction := TLayerAction.Create(Image);
-                        LayerAction.RemoveSelection;
-                        LayerAction.Validate;
-                      except on ex:exception do LazPaintInstance.ShowError(rsTextureMapping,ex.Message);
-                      end;
-                      LayerAction.Free;
+                      FImageActions.RemoveSelection;
                       BGRAReplace(newTexture, newTexture.GetPart(newTexture.GetImageBounds));
                       ToolManager.SetToolTexture(newTexture);
                       UpdateTextureIcon;
@@ -2684,7 +2714,7 @@ begin
           ptLayerMapping:
           begin
             EditDeselect.Execute;
-            if image.SelectedLayerEmpty then
+            if image.CurrentLayerEmpty then
             begin
               MessagePopup(rsEmptyLayer,2000);
               Tool := ptHand;
@@ -2693,7 +2723,7 @@ begin
           end;
           ptMoveLayer:
           begin
-            if image.SelectedLayerEquals(image.SelectedLayerPixel[0,0]) then
+            if image.CurrentLayerEquals(image.CurrentLayerPixel[0,0]) then
             begin
               LazPaintInstance.ShowMessage(rsLazPaint, rsEmptyLayer);
               Tool := ptHand;
@@ -2702,8 +2732,8 @@ begin
           end;
           ptDeformation:
           begin
-            if (image.SelectionEmpty and image.SelectedLayerEquals(image.SelectedLayerPixel[0,0])) or
-               (not image.SelectionEmpty and image.SelectionLayerIsEmpty) then
+            if (image.SelectionMaskEmpty and image.CurrentLayerEquals(image.CurrentLayerPixel[0,0])) or
+               (not image.SelectionMaskEmpty and image.SelectionLayerIsEmpty) then
             begin
               LazPaintInstance.ShowMessage(rsLazPaint, rsNothingToBeDeformed);
               Tool := ptHand;
@@ -2717,7 +2747,7 @@ begin
               result := srException;
               exit;
             end;
-            if image.CurrentLayerVisible and not image.SelectionEmpty and image.SelectionLayerIsEmpty and not image.SelectedLayerEmpty then
+            if image.CurrentLayerVisible and not image.SelectionMaskEmpty and image.SelectionLayerIsEmpty and not image.CurrentLayerEmpty then
             begin
               topmostInfo := LazPaintInstance.HideTopmost;
               if Config.DefaultRetrieveSelectionAnswer <> mrNone then
@@ -2730,21 +2760,7 @@ begin
               end;
               LazPaintInstance.ShowTopmost(topmostInfo);
               case res.ButtonResult of
-                mrYes: begin
-                  LayerAction := nil;
-                  try
-                    LayerAction := TLayerAction.Create(Image);
-                    if LayerAction.RetrieveSelectionIfLayerEmpty(True) then
-                    begin
-                      ComputeSelectionMask(LayerAction.GetOrCreateSelectionLayer,LayerAction.CurrentSelection,Image.SelectionBounds);
-                      Image.SelectionMayChange(Image.SelectionBounds);
-                      LayerAction.Validate;
-                    end;
-                    if image.SelectionLayerIsEmpty then MessagePopup(rsNothingToBeRetrieved,2000);
-                  except on ex:exception do LazPaintInstance.ShowError(rsMovingOrRotatingSelection,ex.Message);
-                  end;
-                  LayerAction.Free;
-                end;
+                mrYes: FImageActions.RetrieveSelection;
               end;
             end;
           end;
@@ -2843,7 +2859,7 @@ end;
 
 procedure TFMain.EditCopyUpdate(Sender: TObject);
 begin
-  EditCopy.Enabled := ToolManager.ToolProvideCopy or not image.SelectionEmpty;
+  EditCopy.Enabled := ToolManager.ToolProvideCopy or not image.SelectionMaskEmpty;
 end;
 
 procedure TFMain.EditCutExecute(Sender: TObject);
@@ -2854,12 +2870,12 @@ end;
 
 procedure TFMain.EditCutUpdate(Sender: TObject);
 begin
-  EditCut.Enabled := ToolManager.ToolProvideCut or not image.SelectionEmpty;
+  EditCut.Enabled := ToolManager.ToolProvideCut or not image.SelectionMaskEmpty;
 end;
 
 procedure TFMain.EditDeleteSelectionUpdate(Sender: TObject);
 begin
-  EditDeleteSelection.Enabled := not image.SelectionEmpty;
+  EditDeleteSelection.Enabled := not image.SelectionMaskEmpty;
 end;
 
 procedure TFMain.EditPasteExecute(Sender: TObject);
@@ -3040,8 +3056,8 @@ end;
 
 procedure TFMain.EditDeselectUpdate(Sender: TObject);
 begin
-  EditDeselect.Enabled := not image.SelectionEmpty;
-  if image.SelectionEmpty then FSaveSelectionInitialFilename := '';
+  EditDeselect.Enabled := not image.SelectionMaskEmpty;
+  if image.SelectionMaskEmpty then FSaveSelectionInitialFilename := '';
 end;
 
 procedure TFMain.EditRedoUpdate(Sender: TObject);
@@ -3060,8 +3076,8 @@ begin
   if Sender is TAction then
   begin
     actionName := (Sender as TAction).Name;
-    if (actionName = 'ImageHorizontalFlip') and not image.SelectionEmpty then actionName := 'SelectionHorizontalFlip' else
-    if (actionName = 'ImageVerticalFlip') and not image.SelectionEmpty  then actionName := 'SelectionVerticalFlip';
+    if (actionName = 'ImageHorizontalFlip') and not image.SelectionMaskEmpty then actionName := 'SelectionHorizontalFlip' else
+    if (actionName = 'ImageVerticalFlip') and not image.SelectionMaskEmpty  then actionName := 'SelectionVerticalFlip';
     CallScriptFunction(actionName);
   end;
 end;
@@ -3069,7 +3085,7 @@ end;
 procedure TFMain.AskMergeSelection(ACaption: string);
 var topmostInfo: TTopMostInfo; res: integer;
 begin
-  if not image.SelectionEmpty and not image.SelectionLayerIsEmpty then
+  if not image.SelectionMaskEmpty and not image.SelectionLayerIsEmpty then
   begin
     topmostInfo:= LazPaintInstance.HideTopmost;
     res := MessageDlg(ACaption,rsMergeSelection,mtConfirmation,[mbYes,mbNo],0);
@@ -3327,12 +3343,22 @@ var
       with ComputeAcceptableImageSize(newPicture.bmp.Width,newPicture.bmp.Height) do
         if (cx < newPicture.bmp.Width) or (cy < newPicture.bmp.Height) then
           BGRAReplace(newPicture.bmp, newPicture.bmp.Resample(cx,cy,rmFineResample));
-      FImageActions.SetCurrentBitmap(newPicture.bmp, False); //image owned
+      image.Assign(newPicture.bmp,True, false);
       newPicture.bmp := nil;
       EndImport(newPicture.bpp, newPicture.frameIndex);
     end else FreeAndNil(newPicture.bmp);
   end;
 
+  procedure ImportSvg;
+  var
+    layered: TBGRALayeredBitmap;
+  begin
+    StartImport;
+    layered := LoadSVGImageUTF8(filenameUTF8);
+    Image.Assign(layered,true, false);
+    EndImport;
+  end;
+
 begin
   result := false;
   if filenameUTF8 = '' then exit;
@@ -3345,6 +3371,10 @@ begin
   newPicture := TImageEntry.Empty;
   try
     format := Image.DetectImageFormat(filenameUTF8);
+    if format = ifSvg then
+    begin
+      ImportSvg;
+    end else
     if Assigned(ALoadedImage) and Assigned(ALoadedImage^.bmp) then
     begin
       newPicture := ALoadedImage^;
@@ -3388,7 +3418,7 @@ end;
 
 procedure TFMain.ToolMoveSelectionUpdate(Sender: TObject);
 begin
-  ToolMoveSelection.Enabled := not image.SelectionEmpty;
+  ToolMoveSelection.Enabled := not image.SelectionMaskEmpty;
 end;
 
 {****************************** Picture ************************}
@@ -3433,6 +3463,7 @@ begin
     ChooseTool(ptHand);
     MessagePopup(rsToolOnInvisibleLayer,5000);
   end;
+  if AEvent.DelayedStackUpdate then FUpdateStackWhenIdle := true;
 end;
 
 procedure TFMain.UpdateEditPicture(ADelayed: boolean = false);
@@ -3455,7 +3486,7 @@ end;
 procedure TFMain.PaintPictureNow;
 begin
   if not visible then exit;
-  StackNeedUpdate := true;
+  UpdateStackOnTimer := true;
   Image.OnImageChanged.NotifyObservers;
   {$IFDEF USEPAINTBOXPICTURE}PaintBox_Picture{$ELSE}self{$ENDIF}.Update;
 end;
@@ -3470,7 +3501,14 @@ end;
 procedure TFMain.PictureSelectedLayerIndexChanged(sender: TLazPaintImage);
 begin
   if not image.CurrentLayerVisible and not ToolManager.ToolCanBeUsed then
-    ChooseTool(ptHand);
+    ChooseTool(ptHand)
+  else
+    ToolManager.ToolOpen;
+end;
+
+procedure TFMain.PictureSelectedLayerIndexChanging(sender: TLazPaintImage);
+begin
+  ToolManager.ToolCloseDontReopen;
 end;
 
 procedure TFMain.SetShowSelectionNormal(const AValue: boolean);

+ 2 - 2
lazpaint/lazpainttype.pas

@@ -132,8 +132,8 @@ type
       bmp: TBGRABitmap;
       bpp: integer;
       frameIndex: integer;
-      class function Empty: TImageEntry;
-      class function NewFrameIndex: integer;
+      class function Empty: TImageEntry; static;
+      class function NewFrameIndex: integer; static;
       procedure FreeAndNil;
     end;
     ArrayOfImageEntry = array of TImageEntry;

+ 1 - 0
lazpaint/maintoolbar.inc

@@ -194,6 +194,7 @@ function TFMain.GetCurrentToolAction: TAction;
 begin
   Case CurrentTool of
   ptMoveLayer: result := LayerMove;
+  ptRotateLayer: result := LayerRotate;
   else result := ActionList1.ActionByName('Tool'+PaintToolTypeStr[CurrentTool]) as TAction;
   end;
 end;

+ 29 - 0
lazpaint/release/i18n/lazpaint.ar.po

@@ -903,6 +903,7 @@ msgid "Median"
 msgstr "متوسط"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "سلبي"
 
@@ -1000,6 +1001,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "قلب الصورة افقيا"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "خط السلبية"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "سلبي"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "تكرار..."
@@ -1024,6 +1035,10 @@ msgstr "تدوير 90° CW"
 msgid "Smart zoom x3"
 msgstr "التكبير الذكي x3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1101,6 +1116,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1344,6 +1364,11 @@ msgctxt "tfmain.menuimage.caption"
 msgid "Image"
 msgstr "صورة"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3430,6 +3455,10 @@ msgstr ""
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr ""

+ 29 - 0
lazpaint/release/i18n/lazpaint.cs.po

@@ -894,6 +894,7 @@ msgid "Median"
 msgstr "Medián"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Negativ"
 
@@ -989,6 +990,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "Převrátit obrázek vodorovně"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Lineární negativ"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Negativ"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Opakovat..."
@@ -1013,6 +1024,10 @@ msgstr "Otočit 90° CW"
 msgid "Smart zoom x3"
 msgstr "Chytré zvětšení x3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1089,6 +1104,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1332,6 +1352,11 @@ msgctxt "tfmain.menuimage.caption"
 msgid "Image"
 msgstr "Obrázek"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3417,6 +3442,10 @@ msgstr ""
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr ""

+ 29 - 0
lazpaint/release/i18n/lazpaint.de.po

@@ -911,6 +911,7 @@ msgid "Median"
 msgstr "Median"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Negativ"
 
@@ -1006,6 +1007,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "Spiegle das Bild horizontal"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Lineares Negativ"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Negativ"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Wiederholen..."
@@ -1030,6 +1041,10 @@ msgstr "Drehung 90° Uhrzeigersinn"
 msgid "Smart zoom x3"
 msgstr "Smart Zoom x3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr "Rote und blaue Kanäle tauschen"
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1106,6 +1121,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1349,6 +1369,11 @@ msgctxt "tfmain.menuimage.caption"
 msgid "Image"
 msgstr "Bild"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3435,6 +3460,10 @@ msgstr "Bilder verbleibend: %1"
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr "Zu viel Ebene"
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr "Bilder Gesamt: %1"

+ 29 - 0
lazpaint/release/i18n/lazpaint.es.po

@@ -891,6 +891,7 @@ msgid "Median"
 msgstr "Mediana"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Negativo"
 
@@ -986,6 +987,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "Voltear imágen horizontalmente"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Negativo linear"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Negativo"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Repetir..."
@@ -1010,6 +1021,10 @@ msgstr "Rotar 90º CW"
 msgid "Smart zoom x3"
 msgstr "Zoom inteligente x3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr "Intercambiar canales rojos y azules"
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1086,6 +1101,11 @@ msgstr "64px"
 msgid "auto"
 msgstr "automático"
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1329,6 +1349,11 @@ msgctxt "tfmain.menuimage.caption"
 msgid "Image"
 msgstr "Imágen"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3433,6 +3458,10 @@ msgstr "Quedan: %1"
 msgid "Tool cannot be used on an invisible layer"
 msgstr "La herramienta no puede ser usada en una capa invisible"
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr "Demasiado capas"
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr "Total: %1"

+ 29 - 0
lazpaint/release/i18n/lazpaint.fi.po

@@ -880,6 +880,7 @@ msgid "Median"
 msgstr "Mediaani"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Negatiivi"
 
@@ -975,6 +976,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr ""
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Lineaarinen negatiivi"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Negatiivi"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr ""
@@ -999,6 +1010,10 @@ msgstr "Kierrä 90° myötäpäivään"
 msgid "Smart zoom x3"
 msgstr ""
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1075,6 +1090,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1318,6 +1338,11 @@ msgctxt "TFMAIN.MENUIMAGE.CAPTION"
 msgid "Image"
 msgstr "Kuva"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.menulanguage.caption
 msgctxt "TFMAIN.MENULANGUAGE.CAPTION"
 msgid "Language"
@@ -3400,6 +3425,10 @@ msgstr ""
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr "Kuvia kaikkiaan: %1"

+ 29 - 0
lazpaint/release/i18n/lazpaint.fr.po

@@ -896,6 +896,7 @@ msgid "Median"
 msgstr "Médiane"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Négatif"
 
@@ -991,6 +992,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "Retournement vertical"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Négatif linéaire"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Négatif"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Répéter..."
@@ -1015,6 +1026,10 @@ msgstr "Rotation 90° (horloge)"
 msgid "Smart zoom x3"
 msgstr "Zoom intelligent x3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr "Echanger les canaux rouge et bleu"
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1091,6 +1106,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1334,6 +1354,11 @@ msgctxt "tfmain.menuimage.caption"
 msgid "Image"
 msgstr "Image"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3436,6 +3461,10 @@ msgstr "Images restantes : %1"
 msgid "Tool cannot be used on an invisible layer"
 msgstr "L'outil ne peut pas être utilisé sur un calque invisible"
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr "Trop de calques"
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr "Nb. total d'images : %1"

+ 29 - 0
lazpaint/release/i18n/lazpaint.ja.po

@@ -900,6 +900,7 @@ msgid "Median"
 msgstr "Median"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "色調反転"
 
@@ -995,6 +996,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "水平方向にイメージをひっくり返す"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Linear negative"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "色調反転"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Repeat..."
@@ -1019,6 +1030,10 @@ msgstr "Rotate 90° CW"
 msgid "Smart zoom x3"
 msgstr "スマートズームx3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1096,6 +1111,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1339,6 +1359,11 @@ msgctxt "tfmain.menuimage.caption"
 msgid "Image"
 msgstr "イメージ"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3422,6 +3447,10 @@ msgstr ""
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr ""

+ 29 - 0
lazpaint/release/i18n/lazpaint.lv.po

@@ -897,6 +897,7 @@ msgid "Median"
 msgstr "Izlīdzināt"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Negatīvs"
 
@@ -992,6 +993,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "Apmet attēlu caur sānu malu (spoguļattēls)"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Tiešs negatīvs"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Negatīvs"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Flīzēt ..."
@@ -1016,6 +1027,10 @@ msgstr "Pagriezt pa 90°"
 msgid "Smart zoom x3"
 msgstr "Palielināt attēlu trīskārtīgi"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1092,6 +1107,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1335,6 +1355,11 @@ msgctxt "TFMAIN.MENUIMAGE.CAPTION"
 msgid "Image"
 msgstr "Attēls"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.menulanguage.caption
 msgctxt "TFMAIN.MENULANGUAGE.CAPTION"
 msgid "Language"
@@ -3418,6 +3443,10 @@ msgstr "Palicis: %1"
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr "Attēlu kopskaits: %1"

+ 29 - 0
lazpaint/release/i18n/lazpaint.nl.po

@@ -918,6 +918,7 @@ msgid "Median"
 msgstr "Mediaan"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Negatief"
 
@@ -1013,6 +1014,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "Afbeelding horizontaal spiegelen"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Lineair negatief"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Negatief"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Herhalen..."
@@ -1037,6 +1048,10 @@ msgstr "90° naar rechts draaien"
 msgid "Smart zoom x3"
 msgstr "Slimme zoom x3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1113,6 +1128,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1357,6 +1377,11 @@ msgctxt "tfmain.menuimage.caption"
 msgid "Image"
 msgstr "Afbeelding"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr "-"
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3444,6 +3469,10 @@ msgstr ""
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr ""

+ 29 - 0
lazpaint/release/i18n/lazpaint.po

@@ -880,6 +880,7 @@ msgid "Median"
 msgstr ""
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr ""
 
@@ -975,6 +976,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr ""
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "TFMAIN.IMAGELINEARNEGATIVE.CAPTION"
+msgid "Linear negative"
+msgstr ""
+
+#: tfmain.imagenegative.caption
+msgctxt "TFMAIN.IMAGENEGATIVE.CAPTION"
+msgid "Negative"
+msgstr ""
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr ""
@@ -999,6 +1010,10 @@ msgstr ""
 msgid "Smart zoom x3"
 msgstr ""
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1075,6 +1090,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "TFMAIN.ITEMQUITSEPARATOR.CAPTION"
+msgid "-"
+msgstr ""
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "TFMAIN.ITEMUSEIMAGEBROWSER.CAPTION"
 msgid "Use image browser"
@@ -1318,6 +1338,11 @@ msgctxt "TFMAIN.MENUIMAGE.CAPTION"
 msgid "Image"
 msgstr ""
 
+#: tfmain.menuitem1.caption
+msgctxt "TFMAIN.MENUITEM1.CAPTION"
+msgid "-"
+msgstr ""
+
 #: tfmain.menulanguage.caption
 msgctxt "TFMAIN.MENULANGUAGE.CAPTION"
 msgid "Language"
@@ -3399,6 +3424,10 @@ msgstr ""
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr ""

+ 29 - 0
lazpaint/release/i18n/lazpaint.pt_BR.po

@@ -904,6 +904,7 @@ msgid "Median"
 msgstr "Mediano"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Negativo"
 
@@ -999,6 +1000,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "Inverter imagem horizontalmente"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Negativo linear"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Negativo"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Repetir..."
@@ -1023,6 +1034,10 @@ msgstr "Girar 90° CW"
 msgid "Smart zoom x3"
 msgstr "Zoom inteligente x3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1099,6 +1114,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1342,6 +1362,11 @@ msgctxt "TFMAIN.MENUIMAGE.CAPTION"
 msgid "Image"
 msgstr "Imagem"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3426,6 +3451,10 @@ msgstr ""
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr ""

+ 29 - 0
lazpaint/release/i18n/lazpaint.ru.po

@@ -898,6 +898,7 @@ msgid "Median"
 msgstr "Медиана"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Негатив"
 
@@ -993,6 +994,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "Отразить изображение по горизонтали"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Линейный негатив"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Негатив"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Повторить ..."
@@ -1017,6 +1028,10 @@ msgstr "Поворот на 90° по часовой стрелке"
 msgid "Smart zoom x3"
 msgstr "Интеллектуальное увеличение x3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1093,6 +1108,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1336,6 +1356,11 @@ msgctxt "tfmain.menuimage.caption"
 msgid "Image"
 msgstr "Изображение"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3420,6 +3445,10 @@ msgstr ""
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr ""

+ 29 - 0
lazpaint/release/i18n/lazpaint.sv.po

@@ -886,6 +886,7 @@ msgid "Median"
 msgstr "Median"
 
 #: tfmain.filternegative.caption
+msgctxt "tfmain.filternegative.caption"
 msgid "Negative"
 msgstr "Negativ"
 
@@ -981,6 +982,16 @@ msgctxt "tfmain.imagehorizontalflip.hint"
 msgid "Flip image horizontally"
 msgstr "Vänd bild horisontalt"
 
+#: tfmain.imagelinearnegative.caption
+msgctxt "tfmain.imagelinearnegative.caption"
+msgid "Linear negative"
+msgstr "Linjär negativ"
+
+#: tfmain.imagenegative.caption
+msgctxt "tfmain.imagenegative.caption"
+msgid "Negative"
+msgstr "Negativ"
+
 #: tfmain.imagerepeat.caption
 msgid "Repeat..."
 msgstr "Upprepa..."
@@ -1005,6 +1016,10 @@ msgstr "Rotera 90° medurs"
 msgid "Smart zoom x3"
 msgstr "Smart zoom x3"
 
+#: tfmain.imageswapredblue.caption
+msgid "Swap red and blue channels"
+msgstr ""
+
 #: tfmain.imageverticalflip.caption
 msgctxt "tfmain.imageverticalflip.caption"
 msgid "Vertical flip"
@@ -1081,6 +1096,11 @@ msgstr ""
 msgid "auto"
 msgstr ""
 
+#: tfmain.itemquitseparator.caption
+msgctxt "tfmain.itemquitseparator.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.itemuseimagebrowser.caption
 msgctxt "tfmain.itemuseimagebrowser.caption"
 msgid "Use image browser"
@@ -1324,6 +1344,11 @@ msgctxt "tfmain.menuimage.caption"
 msgid "Image"
 msgstr "Bild"
 
+#: tfmain.menuitem1.caption
+msgctxt "tfmain.menuitem1.caption"
+msgid "-"
+msgstr ""
+
 #: tfmain.menulanguage.caption
 msgctxt "tfmain.menulanguage.caption"
 msgid "Language"
@@ -3407,6 +3432,10 @@ msgstr ""
 msgid "Tool cannot be used on an invisible layer"
 msgstr ""
 
+#: uresourcestrings.rstoomanylayers
+msgid "Too many layers"
+msgstr ""
+
 #: uresourcestrings.rstotalimages
 msgid "Total images: %1"
 msgstr ""

+ 1 - 1
lazpaint/uadjustcurves.pas

@@ -818,7 +818,7 @@ var
 begin
   tempParameters := AParameters.Duplicate;
   try
-    FFilterConnector := TFilterConnector.Create(AInstance, tempParameters);
+    FFilterConnector := TFilterConnector.Create(AInstance, tempParameters, false);
     FFilterConnector.OnTryStopAction := @OnTryStopAction;
   except
     on ex: Exception do

+ 25 - 11
lazpaint/ucanvassize.pas

@@ -5,9 +5,9 @@ unit UCanvassize;
 interface
 
 uses
-  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
-  StdCtrls, Spin, BGRAVirtualScreen, LazPaintType, uscaledpi, uresourcestrings,
-  BGRABitmap, BGRALayers, uimage;
+  Classes, SysUtils, Types, FileUtil, LResources, Forms, Controls, Graphics,
+  Dialogs, StdCtrls, Spin, BGRAVirtualScreen, LazPaintType, uscaledpi,
+  uresourcestrings, BGRABitmap, BGRALayers, BGRALayerOriginal, uimage;
 
 type
   { TFCanvasSize }
@@ -48,20 +48,32 @@ type
 
 implementation
 
-uses ugraph, bgrabitmaptypes, umac;
+uses ugraph, bgrabitmaptypes, umac, BGRATransform;
 
 function ChangeLayeredImageCanvasSize(layeredBmp: TLazPaintImage; newWidth,
   newHeight: integer; anchor: string; background: TBGRAPixel;
   repeatImage: boolean; flipMode: boolean): TBGRALayeredBitmap;
 var i,idx: integer;
+  orig: TBGRALayerCustomOriginal;
+  newOrigin: TPoint;
+  newBmp: TBGRABitmap;
 begin
   result := TBGRALayeredBitmap.Create;
   for i := 0 to layeredbmp.NbLayers-1 do
   begin
-    idx := result.AddOwnedLayer(ChangeCanvasSize(layeredbmp.LayerBitmap[i],newwidth,newHeight,anchor,background,repeatImage,flipMode),
-      layeredBmp.BlendOperation[i],layeredbmp.LayerOpacity[i]);
+    newBmp := ChangeCanvasSize(layeredbmp.LayerBitmap[i],layeredbmp.LayerOffset[i],layeredBmp.Width,layeredBmp.Height, newwidth,newHeight,anchor,background,repeatImage,flipMode);
+    idx := result.AddOwnedLayer(newBmp,layeredBmp.BlendOperation[i],layeredbmp.LayerOpacity[i]);
     result.LayerName[idx] := layeredbmp.LayerName[i];
     result.LayerVisible[idx] := layeredbmp.LayerVisible[i];
+    if not repeatImage and layeredBmp.LayerOriginalDefined[i] and layeredBmp.LayerOriginalKnown[i] then
+    begin
+      orig := layeredBmp.LayerOriginal[i];
+      if result.IndexOfOriginal(orig)=-1 then result.AddOriginal(orig,false);
+      result.LayerOriginalGuid[idx] := orig.Guid;
+      newOrigin := ChangeCanvasSizeOrigin(layeredBmp.Width,layeredBmp.Height,newwidth,newHeight,anchor);
+      result.LayerOriginalMatrix[idx] := AffineMatrixTranslation(newOrigin.X,newOrigin.Y)*layeredBmp.LayerOriginalMatrix[i];
+      result.RenderLayerFromOriginal(idx);
+    end;
   end;
 end;
 
@@ -110,13 +122,15 @@ begin
     ModalResult := mrCancel else
     begin
 
-      canvasSizeResult.layeredBitmap := ChangeLayeredImageCanvasSize(LazPaintInstance.Image,tx,
-         ty,SelectedAnchor,BGRAPixelTransparent, repeatImage, CheckBox_FlipMode.Checked);
-      if LazPaintInstance.Image.SelectionReadonly <> nil then
-        canvasSizeResult.selection := ChangeCanvasSize(LazPaintInstance.Image.SelectionReadonly,tx,
-          ty,SelectedAnchor,BGRABlack, repeatImage, CheckBox_FlipMode.Checked);
+      canvasSizeResult.layeredBitmap := ChangeLayeredImageCanvasSize(LazPaintInstance.Image,
+         tx,ty,SelectedAnchor,BGRAPixelTransparent, repeatImage, CheckBox_FlipMode.Checked);
+      if LazPaintInstance.Image.SelectionMaskReadonly <> nil then
+        canvasSizeResult.selection := ChangeCanvasSize(LazPaintInstance.Image.SelectionMaskReadonly,
+          Point(0,0),LazPaintInstance.Image.Width,LazPaintInstance.Image.Height,
+          tx,ty,SelectedAnchor,BGRABlack, repeatImage, CheckBox_FlipMode.Checked);
       if LazPaintInstance.Image.SelectionLayerReadonly <> nil then
         canvasSizeResult.selectionLayer := ChangeCanvasSize(LazPaintInstance.Image.SelectionLayerReadonly,
+           Point(0,0),LazPaintInstance.Image.Width,LazPaintInstance.Image.Height,
            tx,ty,SelectedAnchor,BGRAPixelTransparent, repeatImage, CheckBox_FlipMode.Checked);
 
       ModalResult := mrOK;

+ 1 - 1
lazpaint/ucolorintensity.pas

@@ -220,7 +220,7 @@ var topmostInfo: TTopMostInfo;
 begin
   FMode := AMode;
   try
-    FFilterConnector := TFilterConnector.Create(AInstance,AParameters);
+    FFilterConnector := TFilterConnector.Create(AInstance,AParameters,false);
     FFilterConnector.OnTryStopAction := @OnTryStopAction;
   except
     on ex: exception do

+ 1 - 1
lazpaint/ucolorize.pas

@@ -298,7 +298,7 @@ var gsbaOptionFromConfig: boolean;
     topmostInfo: TTopMostInfo;
 begin
   try
-    FFilterConnector := TFilterConnector.Create(AInstance,AParameters);
+    FFilterConnector := TFilterConnector.Create(AInstance,AParameters,false);
     FFilterConnector.OnTryStopAction := @OnTryStopAction;
   except
     on ex: exception do

+ 2 - 2
lazpaint/ucommandline.pas

@@ -119,7 +119,7 @@ begin
           val(funcParams[10],o1.y,errPos);
           val(funcParams[11],o2.x,errPos);
           val(funcParams[12],o2.y,errPos);
-          layerAction := TLayerAction.Create(instance.Image);
+          layerAction := instance.Image.CreateAction(true);
           layerAction.DrawingLayer.GradientFill(0,0,
             instance.Image.Width,instance.Image.Height,
             c1,c2,gt,o1,o2,dmDrawWithTransparency,True,False);
@@ -142,7 +142,7 @@ begin
             errorEncountered := true;
             exit;
           end;
-          layerAction := TLayerAction.Create(instance.Image);
+          layerAction := instance.Image.CreateAction(true);
           layerAction.DrawingLayer.ApplyGlobalOpacity(opacity);
           layerAction.Validate;
           FreeAndNil(layerAction);

+ 3 - 3
lazpaint/uconfig.pas

@@ -56,7 +56,7 @@ type
     procedure FinalizeColorizePresets;
 
     function DefaultLangage: string;
-    class function ClassGetDefaultLangage(AIni: TIniFile): string;
+    class function ClassGetDefaultLangage(AIni: TIniFile): string; static;
     procedure SetDefaultLangage(value: string);
 
     function GetLastUpdateCheck: TDateTime;
@@ -66,8 +66,8 @@ type
     procedure SetLatestVersion(value: string);
     procedure GetUpdatedLanguages(AList: TStringList);
     procedure SetUpdatedLanguages(AList: TStringList);
-    class procedure ClassGetUpdatedLanguages(AList: TStringList; AIni: TIniFile; AVersion: string);
-    class procedure ClassSetUpdatedLanguages(AList: TStringList; AIni: TIniFile; AVersion: string);
+    class procedure ClassGetUpdatedLanguages(AList: TStringList; AIni: TIniFile; AVersion: string); static;
+    class procedure ClassSetUpdatedLanguages(AList: TStringList; AIni: TIniFile; AVersion: string); static;
     procedure AddUpdatedLanguage(ALang: string);
 
     function Default3dObjectDirectory: string;

+ 3 - 1
lazpaint/ufileextensions.pas

@@ -39,7 +39,9 @@ function GetImageFormatName(AFormat: TBGRAImageFormat): string;
 
 implementation
 
-uses Masks, LazUTF8, UResourceStrings, BGRASVG;
+uses Masks, LazUTF8, UResourceStrings, BGRASVG,
+     BGRALayerOriginal, BGRASVGOriginal, BGRAGradientOriginal,
+     LCVectorOriginal, LCVectorShapes;
 
 function GetSelectedFilterExtensions(const Filter: string;
   FilterIndex: integer; ARemoveLeadingDot: boolean): TStringList;

+ 33 - 13
lazpaint/ufilterconnector.pas

@@ -23,12 +23,14 @@ type
     FWorkArea: TRect;
     FWorkAreaFullySelected: boolean;
     FParameters: TVariableSet;
+    FLayerBackupImage: TBGRABitmap;
     function GetActionDone: boolean;
     function GetActiveLayeOffset: TPoint;
     function GetActiveLayer: TBGRABitmap;
     function GetBackupLayer: TBGRABitmap;
     function GetCurrentSelection: TBGRABitmap;
-    procedure Init(ALazPaintInstance: TLazPaintCustomInstance; AAction: TLayerAction; AOwned: boolean; AParameters: TVariableSet);
+    procedure Init(ALazPaintInstance: TLazPaintCustomInstance; AAction: TLayerAction; AOwned: boolean;
+                   AParameters: TVariableSet; AApplyOfsBefore: boolean);
     procedure OnTryStop({%H-}sender: TCustomLayerAction);
     procedure DiscardAction;
     procedure ApplySelectionMaskOn(AFilteredLayer: TBGRABitmap);
@@ -36,7 +38,7 @@ type
   public
     ApplyOnSelectionLayer: boolean;
     Form: TForm;
-    constructor Create(ALazPaintInstance : TLazPaintCustomInstance; AParameters: TVariableSet);
+    constructor Create(ALazPaintInstance : TLazPaintCustomInstance; AParameters: TVariableSet; AApplyOfsBefore: boolean);
     constructor Create(ALazPaintInstance : TLazPaintCustomInstance; AParameters: TVariableSet; AAction: TLayerAction; AOwned: boolean);
     destructor Destroy; override;
     procedure ValidateAction;
@@ -58,7 +60,7 @@ type
 
 implementation
 
-uses Types, BGRABitmapTypes;
+uses Types, BGRABitmapTypes, BGRATransform;
 
 { TFilterConnector }
 
@@ -88,7 +90,9 @@ end;
 
 function TFilterConnector.GetBackupLayer: TBGRABitmap;
 begin
-  if ApplyOnSelectionLayer then
+  if Assigned(FLayerBackupImage) then
+    result := FLayerBackupImage
+  else if ApplyOnSelectionLayer then
     result := FAction.BackupSelectionLayer
   else
     result := FAction.BackupSelectedLayer;
@@ -96,17 +100,18 @@ end;
 
 function TFilterConnector.GetCurrentSelection: TBGRABitmap;
 begin
-  if ApplyOnSelectionLayer or FLazPaintInstance.Image.SelectionEmpty then
+  if ApplyOnSelectionLayer or FLazPaintInstance.Image.SelectionMaskEmpty then
     result := nil
   else
   begin
-    result := FLazPaintInstance.Image.SelectionReadonly;
+    result := FLazPaintInstance.Image.SelectionMaskReadonly;
     if (result.Width <> ActiveLayer.Width) or (result.Height <> ActiveLayer.Height) then
       result := nil;
   end;
 end;
 
-procedure TFilterConnector.Init(ALazPaintInstance: TLazPaintCustomInstance; AAction: TLayerAction; AOwned: boolean; AParameters: TVariableSet);
+procedure TFilterConnector.Init(ALazPaintInstance: TLazPaintCustomInstance; AAction: TLayerAction; AOwned: boolean;
+                                AParameters: TVariableSet; AApplyOfsBefore: boolean);
 var sel: TBGRABitmap;
   y,x: integer;
   p : PBGRAPixel;
@@ -114,13 +119,27 @@ var sel: TBGRABitmap;
 begin
   FLazPaintInstance := ALazPaintInstance;
   FParameters := AParameters;
+  ApplyOnSelectionLayer:= not FLazPaintInstance.Image.SelectionLayerIsEmpty;
+
+  if AAction = nil then
+    AAction := ALazPaintInstance.Image.CreateAction(AApplyOfsBefore and not ApplyOnSelectionLayer);
+
+  if ApplyOnSelectionLayer and not IsAffineMatrixIdentity(AAction.SelectionTransform) then
+  begin
+    AAction.ApplySelectionTransform;
+    FLayerBackupImage := AAction.GetOrCreateSelectionLayer.Duplicate as TBGRABitmap;
+  end else
+  begin
+    FLayerBackupImage := nil;
+  end;
+
   FAction := AAction;
   FActionOwned:= AOwned;
   FAction.OnTryStop := @OnTryStop;
-  ApplyOnSelectionLayer:= not FLazPaintInstance.Image.SelectionLayerIsEmpty;
+
   sel := CurrentSelection;
   if sel <> nil then
-    FWorkArea := FLazPaintInstance.Image.SelectionBounds
+    FWorkArea := FLazPaintInstance.Image.SelectionMaskBounds
   else
     FWorkArea := rect(0,0,ActiveLayer.Width,ActiveLayer.Height);
   FWorkAreaFullySelected := true;
@@ -159,19 +178,20 @@ begin
   end;
 end;
 
-constructor TFilterConnector.Create(ALazPaintInstance : TLazPaintCustomInstance; AParameters: TVariableSet);
+constructor TFilterConnector.Create(ALazPaintInstance : TLazPaintCustomInstance; AParameters: TVariableSet; AApplyOfsBefore: boolean);
 begin
-  Init(ALazPaintInstance,TLayerAction.Create(ALazPaintInstance.Image),True,AParameters);
+  Init(ALazPaintInstance,nil,True,AParameters,AApplyOfsBefore);
 end;
 
 constructor TFilterConnector.Create(ALazPaintInstance : TLazPaintCustomInstance; AParameters: TVariableSet; AAction: TLayerAction; AOwned: boolean);
 begin
-  Init(ALazPaintInstance,AAction,AOwned,AParameters);
+  Init(ALazPaintInstance,AAction,AOwned,AParameters,false);
 end;
 
 destructor TFilterConnector.Destroy;
 begin
   DiscardAction;
+  FLayerBackupImage.Free;
   inherited Destroy;
 end;
 
@@ -189,7 +209,7 @@ procedure TFilterConnector.InvalidateActiveLayer(ARect: TRect);
 begin
   if IntersectRect(ARect, ARect, FWorkArea) then
   begin
-    with FLazPaintInstance.Image.LayerOffset[FLazPaintInstance.Image.currentImageLayerIndex] do
+    with FLazPaintInstance.Image.LayerOffset[FLazPaintInstance.Image.CurrentLayerIndex] do
       OffsetRect(ARect, X,Y);
     FLazPaintInstance.NotifyImageChange(True, ARect);
   end;

+ 10 - 2
lazpaint/ufilters.pas

@@ -87,20 +87,28 @@ var
 
 var
   layer: TBGRABitmap;
+  applyOfsBefore: Boolean;
 
 begin
   result := false;
   if filter = pfNone then exit;
   if not AInstance.Image.CheckNoAction then exit;
   if not AInstance.image.CheckCurrentLayerVisible then exit;
-  if (filter = pfLinearNegative) and AInstance.Image.SelectionEmpty and (AInstance.Image.NbLayers = 1) then
+  if (filter = pfLinearNegative) and AInstance.Image.SelectionMaskEmpty and (AInstance.Image.NbLayers = 1) then
   begin
       AInstance.Image.LinearNegativeAll;
       result := true;
       exit;
   end;
+
+  applyOfsBefore:= false;
+  if not (filter in[pfSharpen, pfSmooth, pfClearType, pfClearTypeInverse, pfNormalize, pfMedian,
+            pfNegative, pfLinearNegative, pfComplementaryColor, pfGrayscale]) then
+    if AInstance.Image.SelectionLayerIsEmpty then
+      applyOfsBefore := true;
+
   try
-    FilterConnector := TFilterConnector.Create(AInstance, AParameters);
+    FilterConnector := TFilterConnector.Create(AInstance, AParameters, applyOfsBefore);
     layer := FilterConnector.ActiveLayer;
 
     filteredLayer := nil;

+ 26 - 14
lazpaint/ugraph.pas

@@ -30,7 +30,8 @@ function NicePoint(bmp: TBGRABitmap; ptF: TPointF; alpha: byte = 192):TRect; ove
 procedure NiceLine(bmp: TBGRABitmap; x1,y1,x2,y2: single; alpha: byte = 192);
 function NiceText(bmp: TBGRABitmap; x,y,bmpWidth,bmpHeight: integer; s: string; align: TAlignment = taLeftJustify; valign: TTextLayout = tlTop): TRect;
 function ComputeColorCircle(tx,ty: integer; light: word; hueCorrection: boolean = true): TBGRABitmap;
-function ChangeCanvasSize(bmp: TBGRABitmap; newWidth,newHeight: integer; anchor: string; background: TBGRAPixel; repeatImage: boolean; flipMode: boolean = false): TBGRABitmap; overload;
+function ChangeCanvasSizeOrigin(oldWidth,oldHeight,newWidth, newHeight: integer; anchor: string): TPoint;
+function ChangeCanvasSize(bmp: TBGRABitmap; ofs: TPoint; oldWidth,oldHeight,newWidth,newHeight: integer; anchor: string; background: TBGRAPixel; repeatImage: boolean; flipMode: boolean = false): TBGRABitmap; overload;
 
 procedure RenderCloudsOn(bmp: TBGRABitmap; color: TBGRAPixel);
 procedure RenderWaterOn(bmp: TBGRABitmap; waterColor, skyColor: TBGRAPixel);
@@ -1251,7 +1252,20 @@ begin
   end;
 end;
 
-function ChangeCanvasSize(bmp: TBGRABitmap; newWidth, newHeight: integer;
+function ChangeCanvasSizeOrigin(oldWidth,oldHeight,newWidth, newHeight: integer; anchor: string): TPoint;
+var
+  origin: TPoint;
+begin
+  origin := Point((newWidth div 2)-(oldWidth div 2),(newHeight div 2)-(oldHeight div 2));
+  anchor := UTF8LowerCase(anchor);
+  if (anchor='topleft') or (anchor='top') or (anchor='topright') then origin.Y := 0;
+  if (anchor='bottomleft') or (anchor='bottom') or (anchor='bottomright') then origin.Y := newHeight-oldHeight;
+  if (anchor='topleft') or (anchor='left') or (anchor='bottomleft') then origin.X := 0;
+  if (anchor='topright') or (anchor='right') or (anchor='bottomright') then origin.X := newWidth-oldWidth;
+  result := origin;
+end;
+
+function ChangeCanvasSize(bmp: TBGRABitmap; ofs: TPoint; oldWidth,oldHeight,newWidth, newHeight: integer;
   anchor: string; background: TBGRAPixel; repeatImage: boolean; flipMode: boolean = false): TBGRABitmap;
 var origin: TPoint;
     xb,yb: integer;
@@ -1261,21 +1275,19 @@ var origin: TPoint;
 begin
    if (newWidth < 1) or (newHeight < 1) then
      raise exception.Create('Invalid canvas size');
-   origin := Point((newWidth-bmp.Width) div 2,(newHeight-bmp.Height) div 2);
-   anchor := UTF8LowerCase(anchor);
-   if (anchor='topleft') or (anchor='top') or (anchor='topright') then origin.Y := 0;
-   if (anchor='bottomleft') or (anchor='bottom') or (anchor='bottomright') then origin.Y := newHeight-bmp.Height;
-   if (anchor='topleft') or (anchor='left') or (anchor='bottomleft') then origin.X := 0;
-   if (anchor='topright') or (anchor='right') or (anchor='bottomright') then origin.X := newWidth-bmp.Width;
+   origin := ChangeCanvasSizeOrigin(oldWidth, oldHeight, newWidth, newHeight, anchor);
+   inc(origin.x, ofs.x);
+   inc(origin.y, ofs.y);
+
    result := TBGRABitmap.Create(newWidth,newHeight, background);
-   dx := bmp.Width;
-   dy := bmp.Height;
+   dx := oldWidth;
+   dy := oldHeight;
    if repeatImage then
    begin
-     minx := (0-origin.X-bmp.Width+1) div bmp.Width;
-     miny := (0-origin.Y-bmp.Height+1) div bmp.Height;
-     maxx := (newWidth-origin.X+bmp.Width-1) div bmp.Width;
-     maxy := (newHeight-origin.Y+bmp.Height-1) div bmp.Height;
+     minx := (0-origin.X-oldWidth+1) div oldWidth;
+     miny := (0-origin.Y-oldHeight+1) div oldHeight;
+     maxx := (newWidth-origin.X+oldWidth-1) div oldWidth;
+     maxy := (newHeight-origin.Y+oldHeight-1) div oldHeight;
    end else
    begin
      minx := 0;

Fișier diff suprimat deoarece este prea mare
+ 294 - 323
lazpaint/uimage.pas


+ 235 - 136
lazpaint/uimageaction.pas

@@ -6,7 +6,7 @@ interface
 
 uses
   Classes, SysUtils, LazPaintType, BGRABitmap, UImage, UTool, UScripting,
-  ULayerAction, UImageType, BGRABitmapTypes;
+  ULayerAction, UImageType, BGRABitmapTypes, BGRALayerOriginal, BGRASVGOriginal;
 
 type
 
@@ -21,6 +21,7 @@ type
     procedure ChooseTool(ATool: TPaintToolType);
     procedure RegisterScripts(ARegister: Boolean);
     function GenericScriptFunction(AVars: TVariableSet): TScriptResult;
+    procedure ReleaseSelection;
   public
     constructor Create(AInstance: TLazPaintCustomInstance);
     destructor Destroy; override;
@@ -37,17 +38,22 @@ type
     procedure RotateCW;
     procedure RotateCCW;
     procedure LinearNegativeAll;
+    procedure NegativeAll;
+    procedure SwapRedBlueAll;
     procedure InvertSelection;
     procedure Deselect;
     procedure CopySelection;
     procedure CutSelection;
+    procedure RetrieveSelection;
     procedure DeleteSelection;
+    procedure RemoveSelection;
     procedure Paste;
     procedure PasteAsNewLayer;
     procedure SelectAll;
     procedure SelectionFit;
-    procedure NewLayer;
-    procedure NewLayer(ALayer: TBGRABitmap; AName: string; ABlendOp: TBlendOperation);
+    procedure NewLayer; overload;
+    function NewLayer(ALayer: TBGRABitmap; AName: string; ABlendOp: TBlendOperation): boolean; overload;
+    function NewLayer(ALayer: TBGRALayerCustomOriginal; AName: string; ABlendOp: TBlendOperation): boolean; overload;
     procedure DuplicateLayer;
     procedure MergeLayerOver;
     procedure RemoveLayer;
@@ -55,6 +61,7 @@ type
     procedure Import3DObject(AFilenameUTF8: string);
     function TryAddLayerFromFile(AFilenameUTF8: string; ALoadedImage: TBGRABitmap = nil): boolean;
     function AddLayerFromBitmap(ABitmap: TBGRABitmap; AName: string): boolean;
+    function AddLayerFromOriginal(AOriginal: TBGRALayerCustomOriginal; AName: string): boolean;
     function LoadSelection(AFilenameUTF8: string; ALoadedImage: PImageEntry = nil): boolean;
     property Image: TLazPaintImage read GetImage;
     property ToolManager: TToolManager read GetToolManager;
@@ -64,7 +71,8 @@ type
 implementation
 
 uses Controls, Dialogs, UResourceStrings, UObject3D,
-     ULoadImage, UGraph, UClipboard, Types;
+     ULoadImage, UGraph, UClipboard, Types, BGRAGradientOriginal,
+     BGRATransform, ULoading;
 
 { TImageActions }
 
@@ -75,7 +83,10 @@ end;
 
 function TImageActions.GetCurrentTool: TPaintToolType;
 begin
-  result := FInstance.ToolManager.GetCurrentToolType;
+  if FInstance.ToolManager.CurrentTool = nil then
+    result := ptHand
+  else
+    result := FInstance.ToolManager.GetCurrentToolType;
 end;
 
 function TImageActions.GetToolManager: TToolManager;
@@ -103,6 +114,9 @@ begin
   Scripting.RegisterScriptFunction('ImageFillBackground',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('ImageRotateCW',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('ImageRotateCCW',@GenericScriptFunction,ARegister);
+  Scripting.RegisterScriptFunction('ImageLinearNegative',@GenericScriptFunction,ARegister);
+  Scripting.RegisterScriptFunction('ImageNegative',@GenericScriptFunction,ARegister);
+  Scripting.RegisterScriptFunction('ImageSwapRedBlue',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditUndo',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditRedo',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditInvertSelection',@GenericScriptFunction,ARegister);
@@ -153,6 +167,9 @@ begin
   if f = 'ImageFillBackground' then FillBackground else
   if f = 'ImageRotateCW' then RotateCW else
   if f = 'ImageRotateCCW' then RotateCCW else
+  if f = 'ImageLinearNegative' then LinearNegativeAll else
+  if f = 'ImageNegative' then NegativeAll else
+  if f = 'ImageSwapRedBlue' then SwapRedBlueAll else
   if f = 'EditUndo' then Undo else
   if f = 'EditRedo' then Redo else
   if f = 'EditInvertSelection' then InvertSelection else
@@ -184,7 +201,7 @@ begin
   try
     c := ToolManager.ToolBackColor;
     c.alpha := 255;
-    LayerAction := TLayerAction.Create(Image);
+    LayerAction := Image.CreateAction(true);
     LayerAction.SelectedImageLayer.ReplaceColor(BGRAPixelTransparent,c);
     p := LayerAction.SelectedImageLayer.Data;
     for n := LayerAction.SelectedImageLayer.NbPixels-1 downto 0 do
@@ -206,16 +223,22 @@ procedure TImageActions.FillBackground;
 var tempBmp: TBGRABitmap;
     c: TBGRAPixel;
     LayerAction: TLayerAction;
+    y: Integer;
 begin
   if not Image.CheckNoAction then exit;
   LayerAction := nil;
   try
     c := ToolManager.ToolBackColor;
     c.alpha := 255;
-    LayerAction := TLayerAction.Create(Image);
-    tempBmp := TBGRABitmap.Create(image.Width,image.Height,c);
-    tempBmp.PutImage(0,0,LayerAction.SelectedImageLayer,dmDrawWithTransparency);
-    LayerAction.ReplaceSelectedLayer(tempBmp, True);
+    LayerAction := Image.CreateAction(True);
+    tempBmp := TBGRABitmap.Create(LayerAction.SelectedImageLayer.Width,1);
+    for y := 0 to LayerAction.SelectedImageLayer.Height-1 do
+    begin
+       tempBmp.Fill(c);
+       tempBmp.PutImage(0,-y,LayerAction.SelectedImageLayer,dmDrawWithTransparency);
+       LayerAction.SelectedImageLayer.PutImage(0,y,tempBmp,dmSet);
+    end;
+    tempBmp.Free;
     image.LayerMayChangeCompletely(LayerAction.SelectedImageLayer);
     LayerAction.Validate;
   except
@@ -317,11 +340,11 @@ begin
 
     if Image.CheckNoAction then
     begin
-      LayerAction := TLayerAction.Create(Image);
+      LayerAction := Image.CreateAction;
+      LayerAction.RemoveSelection;
       LayerAction.QuerySelection;
-      LayerAction.CurrentSelection.Fill(BGRABlack);
       LayerAction.CurrentSelection.PutImage(0,0,newSelection,dmSet);
-      Image.SelectionMayChangeCompletely;
+      LayerAction.NotifyChange(Image.SelectionMask,Image.SelectionMaskBounds);
       LayerAction.Validate;
       result := true;
     end;
@@ -339,7 +362,7 @@ begin
   if not image.CheckNoAction then exit;
   if not image.CheckCurrentLayerVisible then exit;
   try
-    if image.SelectionEmpty then
+    if image.SelectionMaskEmpty then
     begin
       FInstance.ShowMessage(rsCrop, rsEmptySelection);
       exit;
@@ -384,17 +407,17 @@ begin
     exit;
   end;
   try
-    if image.SelectionEmpty then
+    if image.SelectionMaskEmpty then
     begin
       FInstance.ShowMessage(rsCrop,rsEmptySelection);
       exit;
     end;
-    if not image.SelectionEmpty then
+    if not image.SelectionMaskEmpty then
     begin
-      r := image.SelectionBounds;
+      r := image.SelectionMaskBounds;
       if (r.left = 0) and (r.Top = 0) and (r.right = image.width) and (r.Bottom =image.height) then exit;
       cropped := image.MakeLayeredBitmapAndSelectionCopy;
-      selectedLayer := image.currentImageLayerIndex;
+      selectedLayer := image.CurrentLayerIndex;
       for i := 0 to cropped.layeredBitmap.NbLayers-1 do
       begin
         cropped.layeredBitmap.LayerBitmap[i].ApplyMask(cropped.selection);
@@ -404,7 +427,7 @@ begin
       BGRAReplace(cropped.selection,cropped.selection.GetPart(r));
       if cropped.selectionLayer <> nil then BGRAReplace(cropped.selectionLayer,cropped.selectionLayer.GetPart(r));
       image.Assign(cropped,true,true);
-      image.SetCurrentImageLayerIndex(selectedLayer);
+      image.SetCurrentLayerByIndex(selectedLayer);
     end;
   except
     on ex:Exception do
@@ -425,6 +448,7 @@ end;
 function TImageActions.TryAddLayerFromFile(AFilenameUTF8: string; ALoadedImage: TBGRABitmap = nil): boolean;
 var
   newPicture: TBGRABitmap;
+  svgOrig: TBGRALayerSVGOriginal;
 begin
   result := false;
   if not AbleToLoadUTF8(AFilenameUTF8) then
@@ -434,37 +458,44 @@ begin
     exit;
   end;
   try
-    if Assigned(ALoadedImage) then
-      newPicture := ALoadedImage
-    else
-      newPicture := LoadFlatImageUTF8(AFilenameUTF8).bmp;
-    AddLayerFromBitmap(newPicture, ExtractFileName(AFilenameUTF8));
+    if Image.DetectImageFormat(AFilenameUTF8) = ifSvg then
+    begin
+      svgOrig := LoadSVGOriginalUTF8(AFilenameUTF8);
+      AddLayerFromOriginal(svgOrig, ExtractFileName(AFilenameUTF8));
+      FreeAndNil(ALoadedImage);
+    end else
+    begin
+      if Assigned(ALoadedImage) then
+      begin
+        newPicture := ALoadedImage;
+        ALoadedImage := nil;
+      end
+      else
+        newPicture := LoadFlatImageUTF8(AFilenameUTF8).bmp;
+      AddLayerFromBitmap(newPicture, ExtractFileName(AFilenameUTF8));
+    end;
+
   except
     on ex: Exception do
+    begin
+      ALoadedImage.Free;
       FInstance.ShowError('TryAddLayerFromFile',ex.Message);
+    end;
   end;
 end;
 
-function TImageActions.AddLayerFromBitmap(ABitmap: TBGRABitmap; AName: string
-  ): boolean;
+function TImageActions.AddLayerFromBitmap(ABitmap: TBGRABitmap; AName: string): boolean;
 var
-  layeraction: TLayerAction;
   ratio: single;
   xorMask: TBGRABitmap;
 begin
   if (ABitmap <> nil) and (ABitmap.Width > 0) and (ABitmap.Height > 0) then
   begin
-    if ToolManager.GetCurrentToolType in [ptDeformation,ptRotateSelection,ptMoveSelection,ptTextureMapping,ptLayerMapping] then
+    if CurrentTool in [ptDeformation,ptRotateSelection,ptMoveSelection,ptTextureMapping,ptLayerMapping] then
       ChooseTool(ptHand);
     if image.CheckNoAction then
     begin
-      if not Image.SelectionEmpty then
-      begin
-        layeraction := TLayerAction.Create(image);
-        layeraction.ReleaseSelection;
-        layeraction.Validate;
-        layeraction.Free;
-      end;
+      if not Image.SelectionMaskEmpty then ReleaseSelection;
       if (ABitmap.Width > Image.Width) or (ABitmap.Height > Image.Height) then
       begin
         ratio := 1;
@@ -482,10 +513,17 @@ begin
       end
       else
         xorMask := nil;
-      NewLayer(ABitmap, AName, boTransparent);
-      if Assigned(xorMask) then
-        NewLayer(xorMask, AName + ' (xor)', boXor);
-      result := true;
+      if NewLayer(ABitmap, AName, boTransparent) then
+      begin
+        if Assigned(xorMask) then
+          result := NewLayer(xorMask, AName + ' (xor)', boXor)
+        else
+          result := true;
+      end else
+      begin
+        xorMask.Free;
+        result := false;
+      end;
     end else
     begin
       ABitmap.Free;
@@ -498,44 +536,45 @@ begin
   end;
 end;
 
+function TImageActions.AddLayerFromOriginal(AOriginal: TBGRALayerCustomOriginal;
+  AName: string): boolean;
+begin
+  if AOriginal <> nil then
+  begin
+    if CurrentTool in [ptDeformation,ptRotateSelection,ptMoveSelection,ptTextureMapping,ptLayerMapping] then
+      ChooseTool(ptHand);
+    if image.CheckNoAction then
+    begin
+      if not Image.SelectionMaskEmpty then ReleaseSelection;
+      result := NewLayer(AOriginal, AName, boTransparent);
+    end else
+    begin
+      AOriginal.Free;
+      result := false;
+    end;
+  end else
+  begin
+    AOriginal.Free;
+    result := false;
+  end;
+end;
+
 procedure TImageActions.HorizontalFlip(AOption: TFlipOption);
-var bounds: TRect;
-    LayerAction: TLayerAction;
 begin
   try
     if (AOption = foCurrentLayer) then
+      image.HorizontalFlip(Image.CurrentLayerIndex) else
+    if ((AOption = foAuto) and not image.SelectionMaskEmpty) or (AOption = foSelection) then
     begin
-      if not Image.CheckNoAction then exit;
-      LayerAction := TLayerAction.Create(Image);
-      LayerAction.SelectedImageLayer.HorizontalFlip;
-      Image.LayerMayChangeCompletely(LayerAction.SelectedImageLayer);
-      LayerAction.Validate;
-      LayerAction.Free;
-    end else
-    if ((AOption = foAuto) and not image.SelectionEmpty) or (AOption = foSelection) then
-    begin
-      if not image.SelectionEmpty then
+      if not image.SelectionMaskEmpty then
       begin
         ChooseTool(ptMoveSelection);
         if not Image.CheckNoAction then exit;
-        LayerAction := TLayerAction.Create(Image);
-        LayerAction.AllChangesNotified := true;
-        bounds := Image.SelectionBounds;
-        LayerAction.currentSelection.HorizontalFlip(bounds);
-        LayerAction.NotifyChange(LayerAction.currentSelection,bounds);
-        Image.SelectionMayChange(bounds);
-        if LayerAction.DrawingLayer <> nil then
-        begin
-          LayerAction.DrawingLayer.HorizontalFlip(bounds);
-          LayerAction.NotifyChange(LayerAction.DrawingLayer,bounds);
-          Image.LayerMayChange(LayerAction.DrawingLayer,bounds);
-        end;
-        LayerAction.Validate;
-        LayerAction.Free;
+        Image.SelectionTransform := AffineMatrixTranslation(+Image.Width/2,0)*AffineMatrixScale(-1,1)*AffineMatrixTranslation(-Image.Width/2,0)*Image.SelectionTransform;
       end else
         exit;
     end else
-    if ((AOption = foAuto) and image.SelectionEmpty) or (AOption = foWholePicture) then
+    if ((AOption = foAuto) and image.SelectionMaskEmpty) or (AOption = foWholePicture) then
       image.HorizontalFlip;
   except
     on ex:Exception do
@@ -544,42 +583,21 @@ begin
 end;
 
 procedure TImageActions.VerticalFlip(AOption: TFlipOption);
-var bounds: TRect;
-    LayerAction: TLayerAction;
 begin
   try
     if (AOption = foCurrentLayer) then
+      image.VerticalFlip(Image.CurrentLayerIndex) else
+    if ((AOption = foAuto) and not image.SelectionMaskEmpty) or (AOption = foSelection) then
     begin
-      if not Image.CheckNoAction then exit;
-      LayerAction := TLayerAction.Create(Image);
-      LayerAction.SelectedImageLayer.VerticalFlip;
-      Image.LayerMayChangeCompletely(LayerAction.SelectedImageLayer);
-      LayerAction.Validate;
-      LayerAction.Free;
-    end else
-    if ((AOption = foAuto) and not image.SelectionEmpty) or (AOption = foSelection) then
-    begin
-      if not image.SelectionEmpty then
+      if not image.SelectionMaskEmpty then
       begin
         ChooseTool(ptMoveSelection);
         if not Image.CheckNoAction then exit;
-        LayerAction := TLayerAction.Create(Image);
-        bounds := Image.SelectionBounds;
-        LayerAction.currentSelection.VerticalFlip(bounds);
-        LayerAction.NotifyChange(LayerAction.currentSelection,bounds);
-        Image.SelectionMayChange(bounds);
-        if LayerAction.DrawingLayer <> nil then
-        begin
-          LayerAction.DrawingLayer.VerticalFlip(bounds);
-          LayerAction.NotifyChange(LayerAction.DrawingLayer,bounds);
-          Image.LayerMayChange(LayerAction.DrawingLayer,bounds);
-        end;
-        LayerAction.Validate;
-        LayerAction.Free;
+        Image.SelectionTransform := AffineMatrixTranslation(0,+Image.Height/2)*AffineMatrixScale(1,-1)*AffineMatrixTranslation(0,-Image.Height/2)*Image.SelectionTransform;
       end else
         exit;
     end else
-    if ((AOption = foAuto) and image.SelectionEmpty) or (AOption = foWholePicture) then
+    if ((AOption = foAuto) and image.SelectionMaskEmpty) or (AOption = foWholePicture) then
       image.VerticalFlip;
   except
     on ex:Exception do
@@ -602,6 +620,16 @@ begin
   Image.LinearNegativeAll;
 end;
 
+procedure TImageActions.NegativeAll;
+begin
+  Image.NegativeAll;
+end;
+
+procedure TImageActions.SwapRedBlueAll;
+begin
+  Image.SwapRedBlue;
+end;
+
 procedure TImageActions.InvertSelection;
 var LayerAction: TLayerAction;
     p : PBGRAPixel;
@@ -609,9 +637,10 @@ var LayerAction: TLayerAction;
 begin
   LayerAction := nil;
   try
-    if not ToolManager.IsSelectingTool then ChooseTool(ptSelectRect);
-    LayerAction := TLayerAction.Create(Image);
+    if not (CurrentTool in[ptSelectRect,ptSelectEllipse]) then ChooseTool(ptSelectRect);
+    LayerAction := Image.CreateAction(false);
     LayerAction.QuerySelection;
+    LayerAction.ApplySelectionTransform;
     p := LayerAction.CurrentSelection.Data;
     for n := LayerAction.CurrentSelection.NbPixels-1 downto 0 do
     begin
@@ -621,7 +650,7 @@ begin
     LayerAction.CurrentSelection.InvalidateBitmap;
     LayerAction.CurrentSelection.LinearNegative;
     LayerAction.Validate;
-    Image.SelectionMayChangeCompletely;
+    Image.SelectionMaskMayChangeCompletely;
   except
     on ex:Exception do
       FInstance.ShowError('InvertSelection',ex.Message);
@@ -637,13 +666,7 @@ begin
   if not Image.CheckNoAction then exit;
   LayerAction := nil;
   try
-    if not image.SelectionEmpty then
-    begin
-      LayerAction := TLayerAction.Create(Image);
-      LayerAction.AllChangesNotified:= true;
-      LayerAction.ReleaseSelection;
-      LayerAction.Validate;
-    end;
+    if not image.SelectionMaskEmpty then ReleaseSelection;
   except
     on ex:Exception do
       FInstance.ShowError('Deselect',ex.Message);
@@ -659,9 +682,9 @@ begin
   LayerAction := nil;
   try
     if not image.CheckNoAction then exit;
-    bounds := Image.SelectionBounds;
+    bounds := Image.SelectionMaskBounds;
     if IsRectEmpty(bounds) then exit;
-    LayerAction := TLayerAction.Create(Image);
+    LayerAction := Image.CreateAction;
     LayerAction.ApplySelectionMask;
     if Image.SelectionLayerIsEmpty then
       LayerAction.RetrieveSelection;
@@ -686,12 +709,13 @@ end;
 procedure TImageActions.CutSelection;
 var LayerAction: TLayerAction;
 begin
+  if image.SelectionMaskEmpty then exit;
   if not image.CheckNoAction then exit;
   LayerAction := nil;
   try
-    if image.SelectionEmpty then exit;
     CopySelection;
-    LayerAction := TLayerAction.Create(Image);
+    LayerAction := Image.CreateAction;
+    LayerAction.ApplySelectionTransform;
     if (LayerAction.GetSelectionLayerIfExists = nil) or (LayerAction.GetSelectionLayerIfExists.Empty) then
       LayerAction.EraseSelectionInBitmap;
     LayerAction.RemoveSelection;
@@ -706,17 +730,41 @@ begin
   LayerAction.Free;
 end;
 
-procedure TImageActions.DeleteSelection;
+procedure TImageActions.RetrieveSelection;
 var LayerAction: TLayerAction;
+  r: TRect;
 begin
+  if image.SelectionMaskEmpty then exit;
   if not image.CheckNoAction then exit;
   LayerAction := nil;
   try
-    if image.SelectionEmpty then exit;
+    LayerAction := Image.CreateAction(false);
+    if LayerAction.RetrieveSelectionIfLayerEmpty(True) then
+    begin
+      r := Image.SelectionMaskBounds;
+      ComputeSelectionMask(LayerAction.GetOrCreateSelectionLayer,LayerAction.CurrentSelection,r);
+      LayerAction.NotifyChange(LayerAction.GetOrCreateSelectionLayer, r);
+      LayerAction.Validate;
+    end;
+    if image.SelectionLayerIsEmpty then MessagePopup(rsNothingToBeRetrieved,2000);
+  except on ex:exception do FInstance.ShowError('RetrieveSelection',ex.Message);
+  end;
+  LayerAction.Free;
+end;
 
-    LayerAction := TLayerAction.Create(Image);
+procedure TImageActions.DeleteSelection;
+var LayerAction: TLayerAction;
+begin
+  if image.SelectionMaskEmpty then exit;
+  if not image.CheckNoAction then exit;
+  LayerAction := nil;
+  try
+    LayerAction := Image.CreateAction;
     if Image.SelectionLayerIsEmpty then
+    begin
+      LayerAction.ApplySelectionTransform;
       LayerAction.EraseSelectionInBitmap;
+    end;
     LayerAction.RemoveSelection;
     LayerAction.Validate;
     if (CurrentTool = ptRotateSelection) or
@@ -729,6 +777,34 @@ begin
   LayerAction.Free;
 end;
 
+procedure TImageActions.RemoveSelection;
+var LayerAction: TLayerAction;
+begin
+  if image.SelectionMaskEmpty then exit;
+  if not image.CheckNoAction then exit;
+  LayerAction := nil;
+  try
+    LayerAction := Image.CreateAction;
+    LayerAction.RemoveSelection;
+    LayerAction.Validate;
+    if (CurrentTool = ptRotateSelection) or
+       (CurrentTool = ptMoveSelection) then
+      ChooseTool(ptHand);
+  except on ex:exception do FInstance.ShowError('RemoveSelection',ex.Message);
+  end;
+  LayerAction.Free;
+end;
+
+procedure TImageActions.ReleaseSelection;
+var
+  layeraction: TLayerAction;
+begin
+  layeraction := image.CreateAction;
+  layeraction.ReleaseSelection;
+  layeraction.Validate;
+  layeraction.Free;
+end;
+
 procedure TImageActions.Paste;
 var partial: TBGRABitmap;
     layeraction: TLayerAction;
@@ -741,7 +817,7 @@ begin
       if partial.NbPixels <> 0 then
       begin
         ToolManager.ToolCloseDontReopen;
-        layeraction := TLayerAction.Create(Image);
+        layeraction := Image.CreateAction(true);
         layeraction.ReleaseSelection;
         layeraction.QuerySelection;
         pastePos := Point((image.Width - partial.Width) div 2 - image.ImageOffset.X,
@@ -753,7 +829,7 @@ begin
         layeraction.GetOrCreateSelectionLayer.PutImage(pastePos.x,pastePos.y,partial,dmFastBlend);
         ComputeSelectionMask(layeraction.GetOrCreateSelectionLayer,layeraction.currentSelection,
           rect(pastePos.x,pastePos.y,pastePos.x+partial.Width,pastePos.y+partial.Height));
-        Image.SelectionMayChange(rect(pastePos.x,pastePos.y,pastePos.x+partial.Width,pastePos.y+partial.Height));
+        Image.SelectionMaskMayChange(rect(pastePos.x,pastePos.y,pastePos.x+partial.Width,pastePos.y+partial.Height));
         layeraction.Validate;
         layeraction.Free;
         ChooseTool(ptMoveSelection);
@@ -792,10 +868,10 @@ var LayerAction : TLayerAction;
 begin
   try
     if not ToolManager.IsSelectingTool then ChooseTool(ptSelectRect);
-    LayerAction := TLayerAction.Create(Image);
+    LayerAction := Image.CreateAction;
     LayerAction.QuerySelection;
     LayerAction.currentSelection.Fill(BGRAWhite);
-    Image.SelectionMayChangeCompletely;
+    Image.SelectionMaskMayChangeCompletely;
     LayerAction.Validate;
     LayerAction.Free;
   except
@@ -810,24 +886,24 @@ var LayerAction: TLayerAction;
 begin
   if not image.CheckNoAction then exit;
   try
-    LayerAction := TLayerAction.Create(Image);
-    LayerAction.AllChangesNotified := true;
+    LayerAction := Image.CreateAction;
+    LayerAction.ChangeBoundsNotified := true;
 
-    if image.SelectionEmpty then
+    if image.SelectionMaskEmpty then
     begin
       bounds := rect(0,0,Image.width,image.height);
       LayerAction.QuerySelection;
       LayerAction.currentSelection.Fill(BGRAWhite);
       LayerAction.NotifyChange(LayerAction.currentSelection, bounds);
-      Image.SelectionMayChange(bounds);
+      Image.SelectionMaskMayChange(bounds);
     end else
     begin
       bounds := image.SelectionLayerBounds;
-      Image.SelectionMayChange(bounds);
+      Image.SelectionMaskMayChange(bounds);
       LayerAction.ApplySelectionMask;
       LayerAction.NotifyChange(LayerAction.GetSelectionLayerIfExists, bounds);
-      bounds := image.SelectionBounds;
-      Image.SelectionMayChange(bounds);
+      bounds := image.SelectionMaskBounds;
+      Image.SelectionMaskMayChange(bounds);
     end;
 
     if LayerAction.RetrieveSelectionIfLayerEmpty(True) then
@@ -840,10 +916,10 @@ begin
     LayerAction.NotifyChange(LayerAction.GetOrCreateSelectionLayer, bounds);
     LayerAction.Validate;
     LayerAction.Free;
-    if image.SelectionEmpty then
+    if image.SelectionMaskEmpty then
     begin
       if (CurrentTool = ptRotateSelection) or
-         (CurrentTool  = ptMoveSelection) then
+         (CurrentTool = ptMoveSelection) then
         ChooseTool(ptHand);
     end else
       if not ToolManager.IsSelectingTool then ChooseTool(ptMoveSelection);
@@ -854,29 +930,52 @@ begin
 end;
 
 procedure TImageActions.NewLayer;
-var top: TTopMostInfo;
-    res: integer;
+{var top: TTopMostInfo;
+    res: integer;}
 begin
-  if not image.SelectionLayerIsEmpty then
+  {if not image.SelectionLayerIsEmpty then
   begin
     top := FInstance.HideTopmost;
     res := MessageDlg(rsTransferSelectionToOtherLayer,mtConfirmation,[mbOk,mbCancel],0);
     FInstance.ShowTopmost(top);
     if res <> mrOk then exit;
-  end;
+  end;}
   if image.NbLayers < MaxLayersToAdd then
   begin
     Image.AddNewLayer;
-    FInstance.ScrollLayerStackOnItem(Image.currentImageLayerIndex);
+    FInstance.ScrollLayerStackOnItem(Image.CurrentLayerIndex);
+  end;
+end;
+
+function TImageActions.NewLayer(ALayer: TBGRABitmap; AName: string;
+  ABlendOp: TBlendOperation): boolean;
+begin
+  if image.NbLayers < MaxLayersToAdd then
+  begin
+    Image.AddNewLayer(ALayer, AName, ABlendOp);
+    FInstance.ScrollLayerStackOnItem(Image.CurrentLayerIndex);
+    result := true;
+  end else
+  begin
+    FInstance.ShowMessage(rsLayers, rsTooManyLayers);
+    ALayer.Free;
+    result := false;
   end;
 end;
 
-procedure TImageActions.NewLayer(ALayer: TBGRABitmap; AName: string; ABlendOp: TBlendOperation);
+function TImageActions.NewLayer(ALayer: TBGRALayerCustomOriginal;
+  AName: string; ABlendOp: TBlendOperation): boolean;
 begin
   if image.NbLayers < MaxLayersToAdd then
   begin
     Image.AddNewLayer(ALayer, AName, ABlendOp);
-    FInstance.ScrollLayerStackOnItem(Image.currentImageLayerIndex);
+    FInstance.ScrollLayerStackOnItem(Image.CurrentLayerIndex);
+    result := true;
+  end else
+  begin
+    FInstance.ShowMessage(rsLayers, rsTooManyLayers);
+    ALayer.Free;
+    result := false;
   end;
 end;
 
@@ -885,25 +984,25 @@ begin
   if image.NbLayers < MaxLayersToAdd then
   begin
     Image.DuplicateLayer;
-    FInstance.ScrollLayerStackOnItem(Image.currentImageLayerIndex);
+    FInstance.ScrollLayerStackOnItem(Image.CurrentLayerIndex);
   end;
 end;
 
 procedure TImageActions.MergeLayerOver;
 begin
-  if (Image.currentImageLayerIndex <> -1) and (image.NbLayers > 1) then
+  if (Image.CurrentLayerIndex <> -1) and (image.NbLayers > 1) then
   begin
     Image.MergeLayerOver;
-    FInstance.ScrollLayerStackOnItem(Image.currentImageLayerIndex);
+    FInstance.ScrollLayerStackOnItem(Image.CurrentLayerIndex);
   end;
 end;
 
 procedure TImageActions.RemoveLayer;
 var idx: integer;
 begin
-  if (Image.currentImageLayerIndex <> -1) and (Image.NbLayers > 1) then
+  if (Image.CurrentLayerIndex <> -1) and (Image.NbLayers > 1) then
   begin
-    idx := Image.currentImageLayerIndex;
+    idx := Image.CurrentLayerIndex;
     Image.RemoveLayer;
     FInstance.ScrollLayerStackOnItem(idx);
   end;
@@ -915,7 +1014,7 @@ var lSelection,lTemp: TBGRABitmap;
 begin
   if not image.CheckNoAction then exit;
   try
-    LayerAction := TLayerAction.Create(Image);
+    LayerAction := Image.CreateAction;
     try
       LayerAction.QuerySelection;
       lSelection:= LayerAction.currentSelection.Duplicate as TBGRABitmap;
@@ -937,7 +1036,7 @@ begin
       LayerAction.Validate;
     finally
       LayerAction.Free;
-      Image.SelectionMayChangeCompletely;
+      Image.SelectionMaskMayChangeCompletely;
     end;
   except on ex:Exception do FInstance.ShowError('EditSelection',ex.Message);
   end;

Fișier diff suprimat deoarece este prea mare
+ 584 - 93
lazpaint/uimagediff.pas


+ 148 - 133
lazpaint/uimagepreview.pas

@@ -24,6 +24,7 @@ type
 
     FFilename: string;
     FLoadError: string;
+    FInUpdatePreview: boolean;
 
     FImageFormat: TBGRAImageFormat;
     FImageNbLayers: integer;
@@ -77,6 +78,7 @@ type
     procedure ClearThumbnails;
     procedure DoValidate;
     procedure SetLoopCount;
+    procedure FinishUpdatePreview;
   public
     LazPaintInstance: TLazPaintCustomInstance;
     constructor Create(ASurface: TBGRAVirtualScreen; AStatus: TLabel; AAnimate: boolean);
@@ -100,6 +102,7 @@ uses FPimage, BGRAReadJpeg, BGRAOpenRaster, BGRAPaintNet, BGRAReadLzp, Dialogs,
 
 function TImagePreview.GetPreviewDataLoss: boolean;
 begin
+  FinishUpdatePreview;
   result := (FImageFormat in[ifJpeg,     {compression loss}
                              ifLazPaint, {layer loss}
                              ifOpenRaster,
@@ -741,6 +744,148 @@ begin
   end;
 end;
 
+procedure TImagePreview.FinishUpdatePreview;
+var reader: TFPCustomImageReader;
+  jpegReader: TBGRAReaderJpeg;
+  source: TStream;
+begin
+  if FInUpdatePreview then
+  begin
+    source := nil;
+    try
+      source := FileManager.CreateFileStream(FFilename, fmOpenRead or fmShareDenyWrite);
+      FImageFormat := DetectFileFormat(source,ExtractFileExt(FFilename));
+      case FImageFormat of
+      ifGif:
+        begin
+          try
+            FAnimatedGif := TBGRAAnimatedGif.Create;
+            FAnimatedGif.LoadFromStream(source);
+            FImageNbLayers := 1;
+          except
+            on ex: Exception do
+            begin
+              FLoadError := ex.Message;
+              FreeAndNil(FAnimatedGif);
+            end;
+          end;
+        end;
+      ifTiff:
+        begin
+          try
+            FTiff := TTiff.Create;
+            if FTiff.LoadFromStream(source) <> teNone then
+              raise exception.Create(rsCannotOpenFile);
+
+            FImageNbLayers := 1;
+            if FTiff.Count = 0 then
+            begin
+              FreeAndNil(FTiff);
+              FLoadError := rsFileCannotBeEmpty;
+            end;
+          except
+            on ex: Exception do
+            begin
+              FLoadError := ex.Message;
+              FreeAndNil(FTiff);
+            end;
+          end;
+        end;
+      ifIco,ifCur:
+        begin
+          FIconCursor := TBGRAIconCursor.Create;
+          try
+            FIconCursor.LoadFromStream(source);
+            FImageNbLayers := 1;
+          except
+            on ex: Exception do
+            begin
+              FLoadError:= ex.Message;
+              FreeAndNil(FIconCursor);
+            end;
+          end;
+        end;
+      ifJpeg:
+        begin
+          jpegReader := TBGRAReaderJpeg.Create;
+          jpegReader.Performance := jpBestSpeed;
+          jpegReader.MinWidth := Screen.Width;
+          jpegReader.MinHeight := Screen.Height;
+          try
+            FSingleImage := TBGRABitmap.Create;
+            FSingleImage.LoadFromStream(source,jpegReader);
+            FImageNbLayers := 1;
+          except
+            on ex: Exception do
+            begin
+              FLoadError:= ex.Message;
+              FreeAndNil(FSingleImage);
+            end;
+          end;
+          jpegReader.Free;
+        end;
+      else
+        begin
+          reader := CreateBGRAImageReader(FImageFormat);
+          try
+            FSingleImage := TBGRABitmap.Create;
+            FSingleImage.LoadFromStream(source,reader);
+            if reader is TFPReaderOpenRaster then FImageNbLayers := TFPReaderOpenRaster(reader).NbLayers else
+            if reader is TFPReaderPaintDotNet then FImageNbLayers := TFPReaderPaintDotNet(reader).NbLayers else
+            if reader is TBGRAReaderLazPaint then FImageNbLayers := TBGRAReaderLazPaint(reader).NbLayers else
+            if reader is TBGRAReaderOXO then FImageNbLayers := TBGRAReaderOXO(reader).NbLayers else
+              FImageNbLayers := 1;
+          except
+            on ex: Exception do
+            begin
+              FLoadError:= ex.Message;
+              FreeAndNil(FSingleImage);
+            end;
+          end;
+          reader.Free;
+        end;
+      end;
+    except
+      on ex: Exception do
+        FLoadError:= ex.Message;
+    end;
+    source.Free;
+
+    if Assigned(FIconCursor) then
+    begin
+      if FIconCursor.Count > 0 then
+        FStatus.Caption := rsCanvasSize + ': ' + IntToStr(FIconCursor.Width[0])+'x'+IntToStr(FIconCursor.Height[0])+ ', ' +
+                           rsEntries + ': ' + IntToStr(FIconCursor.Count)
+      else
+        FStatus.Caption := rsEntries + ': ' + IntToStr(FIconCursor.Count);
+    end else
+    if Assigned(FAnimatedGif) then
+    begin
+      FStatus.Caption := rsCanvasSize + ': ' + IntToStr(FAnimatedGif.Width)+'x'+IntToStr(FAnimatedGif.Height)+', '+
+                         rsFrames+': '+IntToStr(FAnimatedGif.Count);
+    end else
+    if Assigned(FTiff) then
+    begin
+      with FTiff.GetBiggestImage do
+        FStatus.Caption := rsCanvasSize + ': ' + IntToStr(Width)+'x'+IntToStr(Height)+', '+
+                           rsEntries+': '+IntToStr(FTiff.Count);
+    end else
+    if Assigned(FSingleImage) then
+    begin
+      FStatus.Caption := rsCanvasSize + ': ' + IntToStr(FSingleImage.Width)+'x'+IntToStr(FSingleImage.Height)+', '+
+                         rsLayers+': '+IntToStr(FImageNbLayers);
+    end else
+    if FLoadError <> '' then
+    begin
+      FStatus.Caption := FLoadError;
+    end else
+      FStatus.Caption := '';
+
+    FInUpdatePreview := false;
+    FSurface.RedrawBitmap;
+  end;
+end;
+
 procedure TImagePreview.DeleteEntry(i: integer);
 var outputStream: TStream;
 begin
@@ -865,9 +1010,6 @@ begin
 end;
 
 procedure TImagePreview.UpdatePreview;
-var reader: TFPCustomImageReader;
-  jpegReader: TBGRAReaderJpeg;
-  source: TStream;
 begin
   ClearThumbnails;
   FreeAndNil(FSingleImage);
@@ -883,139 +1025,11 @@ begin
   FSurface.RedrawBitmap;
   FStatus.Caption := rsLoading+'...';
   FStatus.Update;
+  FInUpdatePreview := true;
   {$IFDEF LINUX}
   Application.ProcessMessages;
   {$ENDIF}
-  source := nil;
-  try
-    source := FileManager.CreateFileStream(FFilename, fmOpenRead or fmShareDenyWrite);
-    FImageFormat := DetectFileFormat(source,ExtractFileExt(FFilename));
-    case FImageFormat of
-    ifGif:
-      begin
-        try
-          FAnimatedGif := TBGRAAnimatedGif.Create;
-          FAnimatedGif.LoadFromStream(source);
-          FImageNbLayers := 1;
-        except
-          on ex: Exception do
-          begin
-            FLoadError := ex.Message;
-            FreeAndNil(FAnimatedGif);
-          end;
-        end;
-      end;
-    ifTiff:
-      begin
-        try
-          FTiff := TTiff.Create;
-          if FTiff.LoadFromStream(source) <> teNone then
-            raise exception.Create(rsCannotOpenFile);
-
-          FImageNbLayers := 1;
-          if FTiff.Count = 0 then
-          begin
-            FreeAndNil(FTiff);
-            FLoadError := rsFileCannotBeEmpty;
-          end;
-        except
-          on ex: Exception do
-          begin
-            FLoadError := ex.Message;
-            FreeAndNil(FTiff);
-          end;
-        end;
-      end;
-    ifIco,ifCur:
-      begin
-        FIconCursor := TBGRAIconCursor.Create;
-        try
-          FIconCursor.LoadFromStream(source);
-          FImageNbLayers := 1;
-        except
-          on ex: Exception do
-          begin
-            FLoadError:= ex.Message;
-            FreeAndNil(FIconCursor);
-          end;
-        end;
-      end;
-    ifJpeg:
-      begin
-        jpegReader := TBGRAReaderJpeg.Create;
-        jpegReader.Performance := jpBestSpeed;
-        jpegReader.MinWidth := Screen.Width;
-        jpegReader.MinHeight := Screen.Height;
-        try
-          FSingleImage := TBGRABitmap.Create;
-          FSingleImage.LoadFromStream(source,jpegReader);
-          FImageNbLayers := 1;
-        except
-          on ex: Exception do
-          begin
-            FLoadError:= ex.Message;
-            FreeAndNil(FSingleImage);
-          end;
-        end;
-        jpegReader.Free;
-      end;
-    else
-      begin
-        reader := CreateBGRAImageReader(FImageFormat);
-        try
-          FSingleImage := TBGRABitmap.Create;
-          FSingleImage.LoadFromStream(source,reader);
-          if reader is TFPReaderOpenRaster then FImageNbLayers := TFPReaderOpenRaster(reader).NbLayers else
-          if reader is TFPReaderPaintDotNet then FImageNbLayers := TFPReaderPaintDotNet(reader).NbLayers else
-          if reader is TBGRAReaderLazPaint then FImageNbLayers := TBGRAReaderLazPaint(reader).NbLayers else
-          if reader is TBGRAReaderOXO then FImageNbLayers := TBGRAReaderOXO(reader).NbLayers else
-            FImageNbLayers := 1;
-        except
-          on ex: Exception do
-          begin
-            FLoadError:= ex.Message;
-            FreeAndNil(FSingleImage);
-          end;
-        end;
-        reader.Free;
-      end;
-    end;
-  except
-    on ex: Exception do
-      FLoadError:= ex.Message;
-  end;
-  source.Free;
-
-  if Assigned(FIconCursor) then
-  begin
-    if FIconCursor.Count > 0 then
-      FStatus.Caption := rsCanvasSize + ': ' + IntToStr(FIconCursor.Width[0])+'x'+IntToStr(FIconCursor.Height[0])+ ', ' +
-                         rsEntries + ': ' + IntToStr(FIconCursor.Count)
-    else
-      FStatus.Caption := rsEntries + ': ' + IntToStr(FIconCursor.Count);
-  end else
-  if Assigned(FAnimatedGif) then
-  begin
-    FStatus.Caption := rsCanvasSize + ': ' + IntToStr(FAnimatedGif.Width)+'x'+IntToStr(FAnimatedGif.Height)+', '+
-                       rsFrames+': '+IntToStr(FAnimatedGif.Count);
-  end else
-  if Assigned(FTiff) then
-  begin
-    with FTiff.GetBiggestImage do
-      FStatus.Caption := rsCanvasSize + ': ' + IntToStr(Width)+'x'+IntToStr(Height)+', '+
-                         rsEntries+': '+IntToStr(FTiff.Count);
-  end else
-  if Assigned(FSingleImage) then
-  begin
-    FStatus.Caption := rsCanvasSize + ': ' + IntToStr(FSingleImage.Width)+'x'+IntToStr(FSingleImage.Height)+', '+
-                       rsLayers+': '+IntToStr(FImageNbLayers);
-  end else
-  if FLoadError <> '' then
-  begin
-    FStatus.Caption := FLoadError;
-  end else
-    FStatus.Caption := '';
-  FSurface.RedrawBitmap;
+  FinishUpdatePreview;
 end;
 
 procedure TImagePreview.HandleTimer;
@@ -1027,6 +1041,7 @@ end;
 function TImagePreview.GetPreviewBitmap: TImageEntry;
 var tx,ty,bpp: integer; back: TBGRAPixel;
 begin
+  FinishUpdatePreview;
   result := TImageEntry.Empty;
 
   if Assigned(FIconCursor) then

Fișier diff suprimat deoarece este prea mare
+ 565 - 142
lazpaint/uimagestate.pas


+ 5 - 3
lazpaint/uimageview.pas

@@ -55,7 +55,7 @@ type
     procedure OnZoomChanged({%H-}sender: TZoom; {%H-}ANewZoom: single; AWorkArea: TRect);
     procedure UpdateCursor(X,Y: integer; ACanvasOfs: TPoint; AWorkArea: TRect; AControl: TControl;
                           AWinControlOfs: TPoint; AWinControl: TWinControl);
-    procedure UpdatePicture(ACanvasOfs: TPoint; AWorkArea: TRect; AControl: TControl);
+    procedure UpdatePicture(ACanvasOfs: TPoint; AWorkArea: TRect; {%H-}AControl: TControl);
     function BitmapToForm(pt: TPointF): TPointF;
     function BitmapToForm(X, Y: Single): TPointF;
     function BitmapToVirtualScreen(ptF: TPointF): TPointF;
@@ -71,7 +71,7 @@ type
 
 implementation
 
-uses BGRATransform, LCLIntf, Types, ugraph, math, UTool;
+uses BGRATransform, LCLIntf, Types, ugraph, math, UTool, BGRAThumbnail;
 
 function TImageView.GetFillSelectionHighlight: boolean;
 begin
@@ -80,7 +80,9 @@ end;
 
 procedure TImageView.SetFillSelectionHighlight(AValue: boolean);
 begin
+  if AValue = FSelectionHighlight.FillSelection then exit;
   FSelectionHighlight.FillSelection := AValue;
+  Image.ImageMayChangeCompletely;
 end;
 
 function TImageView.GetImage: TLazPaintImage;
@@ -151,7 +153,7 @@ begin
   OffsetRect(renderRect, -FLastPictureParameters.virtualScreenArea.Left,
                          -FLastPictureParameters.virtualScreenArea.Top);
 
-  Image.DrawBackground(FVirtualScreen, renderRect);
+  DrawThumbnailCheckers(FVirtualScreen,renderRect,Image.IsIconCursor);
 
   //draw image (with merged selection)
   FVirtualScreen.StretchPutImage(renderRect,Image.RenderedImage,dmDrawWithTransparency);

+ 399 - 183
lazpaint/ulayeraction.pas

@@ -5,18 +5,27 @@ unit ULayerAction;
 interface
 
 uses
-  Classes, SysUtils, UImage, BGRABitmap, BGRABitmapTypes, UImageType;
+  Classes, SysUtils, BGRABitmap, BGRABitmapTypes, UImageType,
+  UStateType, UImageState;
 
 type
+  TNotifyChangeEvent = procedure(ASender: TObject; ALayer: TBGRABitmap; ARect: TRect) of object;
+  TNotifyUndoEvent = procedure(ASender: TObject; AUndo: TCustomImageDifference; var Owned: boolean) of object;
+
   { TLayerAction }
 
   TLayerAction = class(TCustomLayerAction)
   private
-    FAllChangesNotified: boolean;
-    FImage: TLazPaintImage;
+    FChangeBoundsNotified: boolean;
+    FImageState: TImageState;
+    FOnDestroy: TNotifyEvent;
+    FOnNotifyChange: TNotifyChangeEvent;
+    FOnNotifyUndo: TNotifyUndoEvent;
+    FPrediff: TComposedImageDifference;
     FBackupSelectedLayer, FBackupSelectionLayer, FBackupSelection: TBGRABitmap;
-    FBackupSelectedLayerDefined, FBackupSelectionLayerDefined, FBackupSelectionDefined: boolean;
-    FSelectedLayerChangedArea, FSelectionLayerChangedArea, FSelectionChangedArea: TRect;
+    FBackupSelectedLayerDefined, FBackupSelectionLayerDefined, FBackupSelectionMaskDefined: boolean;
+    FBackupSelectionTransform: TAffineMatrix;
+    FSelectedImageLayerChangedArea, FSelectionLayerChangedArea, FSelectionMaskChangedArea: TRect;
     FDone: boolean;
     FOnTryStop: TOnTryStopEventHandler;
     function GetBackupDrawingLayer: TBGRABitmap;
@@ -24,18 +33,23 @@ type
     function GetBackupSelection: TBGRABitmap;
     function GetBackupSelectionLayer: TBGRABitmap;
     function GetCurrentSelection: TBGRABitmap;
+    function GetCurrentState: TImageState;
     function GetSelectedImageLayer: TBGRABitmap;
     function GetDrawingLayer: TBGRABitmap;
     function GetSelectedImageLayerOffset: TPoint;
+    function GetSelectionLayerBounds: TRect;
+    function GetSelectionTransform: TAffineMatrix;
+    procedure SetOnDestroy(AValue: TNotifyEvent);
+    procedure SetOnNotifyChange(AValue: TNotifyChangeEvent);
+    procedure SetOnNotifyUndo(AValue: TNotifyUndoEvent);
   protected
     procedure Cancel;
-    procedure NeedSelectionBackup;
+    procedure NeedSelectionMaskBackup;
     procedure NeedSelectedLayerBackup;
     procedure NeedSelectionLayerBackup;
-    function GetSelectionTransform: TAffineMatrix;
-    procedure SetSelectionTransform(AValue: TAffineMatrix);
+    property CurrentState: TImageState read GetCurrentState;
   public
-    constructor Create(AImage: TLazPaintImage);
+    constructor Create(AState: TImageState; AApplyOfsBefore: boolean = false);
     procedure Validate;
     procedure PartialValidate(ADiscardBackup: boolean = false);
     procedure PartialCancel;
@@ -44,18 +58,17 @@ type
     procedure QuerySelection;
     procedure RemoveSelection;
     procedure EraseSelectionInBitmap;
+    procedure MergeWithSelection(AApplyMask: boolean = true);
     procedure ReleaseSelection;
     procedure RetrieveSelection;
     function RetrieveSelectionIfLayerEmpty(removeFromBitmap: boolean = false): boolean;
     procedure ApplySelectionTransform(ApplyToMask: boolean= true);
     procedure ApplySelectionMask;
 
-    procedure ReplaceSelectedLayer(AValue: TBGRABitmap; AOwned: boolean);
     procedure ReplaceSelectionLayer(bmp: TBGRABitmap; AOwned: boolean);
-    procedure ReplaceDrawingLayer(bmp: TBGRABitmap; AOwned: boolean);
     procedure ReplaceCurrentSelection(AValue: TBGRABitmap);
     procedure NotifyChange(ADest: TBGRABitmap; ARect: TRect);
-    procedure RestoreSelection;
+    procedure RestoreSelectionMask;
     procedure RestoreDrawingLayer;
     procedure RestoreSelectedLayer;
     procedure RestoreSelectionLayer;
@@ -73,27 +86,38 @@ type
     property BackupSelectedLayer: TBGRABitmap read GetBackupSelectedLayer;
     property BackupDrawingLayer: TBGRABitmap read GetBackupDrawingLayer;
     property OnTryStop: TOnTryStopEventHandler read FOnTryStop write FOnTryStop;
-    property SelectionTransform: TAffineMatrix read GetSelectionTransform write SetSelectionTransform;
     property Done: boolean read FDone;
-    property AllChangesNotified: boolean read FAllChangesNotified write FAllChangesNotified;
+    property ChangeBoundsNotified: boolean read FChangeBoundsNotified write FChangeBoundsNotified;
+    property SelectionTransform: TAffineMatrix read GetSelectionTransform;
+    property SelectionLayerBounds: TRect read GetSelectionLayerBounds;
+    property OnNotifyChange: TNotifyChangeEvent read FOnNotifyChange write SetOnNotifyChange;
+    property OnNotifyUndo: TNotifyUndoEvent read FOnNotifyUndo write SetOnNotifyUndo;
+    property OnDestroy: TNotifyEvent read FOnDestroy write SetOnDestroy;
   end;
 
 implementation
 
-uses UResourceStrings, UGraph, Types, Dialogs;
+uses UResourceStrings, UGraph, Types, Dialogs, BGRATransform, BGRALayerOriginal, UImageDiff;
 
 { TLayerAction }
 
 function TLayerAction.GetSelectedImageLayer: TBGRABitmap;
 begin
+  result := CurrentState.SelectedImageLayer;
+  if not Assigned(result) then
+    raise exception.Create('No image layer selected');
   NeedSelectedLayerBackup;
-  result := FImage.GetSelectedImageLayer;
 end;
 
 function TLayerAction.GetCurrentSelection: TBGRABitmap;
 begin
-  NeedSelectionBackup;
-  result := fImage.currentSelection;
+  NeedSelectionMaskBackup;
+  result := CurrentState.SelectionMask;
+end;
+
+function TLayerAction.GetCurrentState: TImageState;
+begin
+  result := FImageState;
 end;
 
 function TLayerAction.GetBackupSelectedLayer: TBGRABitmap;
@@ -104,13 +128,13 @@ end;
 
 function TLayerAction.GetBackupDrawingLayer: TBGRABitmap;
 begin
-  if FImage.SelectionEmpty then result := BackupSelectedLayer else
+  if CurrentState.SelectionMaskEmpty then result := BackupSelectedLayer else
     result := BackupSelectionLayer;
 end;
 
 function TLayerAction.GetBackupSelection: TBGRABitmap;
 begin
-  NeedSelectionBackup;
+  NeedSelectionMaskBackup;
   result := FBackupSelection;
 end;
 
@@ -122,23 +146,41 @@ end;
 
 function TLayerAction.GetDrawingLayer: TBGRABitmap;
 begin
-  if FImage.SelectionEmpty then result := GetSelectedImageLayer else
+  if CurrentState.SelectionMaskEmpty then result := GetSelectedImageLayer else
     result := GetOrCreateSelectionLayer;
 end;
 
 function TLayerAction.GetSelectedImageLayerOffset: TPoint;
 begin
-  result := FImage.LayerOffset[FImage.currentImageLayerIndex];
+  result := CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex];
+end;
+
+function TLayerAction.GetSelectionLayerBounds: TRect;
+begin
+  result := CurrentState.GetSelectionLayerBounds;
 end;
 
 function TLayerAction.GetSelectionTransform: TAffineMatrix;
 begin
-  result := FImage.SelectionTransform;
+  result:= CurrentState.SelectionTransform;
 end;
 
-procedure TLayerAction.SetSelectionTransform(AValue: TAffineMatrix);
+procedure TLayerAction.SetOnDestroy(AValue: TNotifyEvent);
 begin
-  FImage.SelectionTransform := AValue;
+  if FOnDestroy=AValue then Exit;
+  FOnDestroy:=AValue;
+end;
+
+procedure TLayerAction.SetOnNotifyChange(AValue: TNotifyChangeEvent);
+begin
+  if FOnNotifyChange=AValue then Exit;
+  FOnNotifyChange:=AValue;
+end;
+
+procedure TLayerAction.SetOnNotifyUndo(AValue: TNotifyUndoEvent);
+begin
+  if FOnNotifyUndo=AValue then Exit;
+  FOnNotifyUndo:=AValue;
 end;
 
 procedure TLayerAction.Cancel;
@@ -146,16 +188,22 @@ begin
   if FDone then raise Exception.Create('Already done');
   RestoreSelectedLayer;
   RestoreSelectionLayer;
-  RestoreSelection;
+  RestoreSelectionMask;
+  CurrentState.SelectionTransform := FBackupSelectionTransform;
+  if Assigned(FPrediff) then
+  begin
+    FPrediff.UnapplyTo(CurrentState);
+    FreeAndNil(FPrediff);
+  end;
   FDone := true;
 end;
 
-procedure TLayerAction.NeedSelectionBackup;
+procedure TLayerAction.NeedSelectionMaskBackup;
 begin
-  if not FBackupSelectionDefined then
+  if not FBackupSelectionMaskDefined then
   begin
-    FBackupSelection := DuplicateBitmap(FImage.currentSelection);
-    FBackupSelectionDefined := true;
+    FBackupSelection := DuplicateBitmap(CurrentState.SelectionMask);
+    FBackupSelectionMaskDefined := true;
   end;
 end;
 
@@ -163,7 +211,7 @@ procedure TLayerAction.NeedSelectedLayerBackup;
 begin
   if not FBackupSelectedLayerDefined then
   begin
-    FBackupSelectedLayer := DuplicateBitmap(FImage.GetSelectedImageLayer);
+    FBackupSelectedLayer := DuplicateBitmap(CurrentState.SelectedImageLayer);
     FBackupSelectedLayerDefined := true;
   end;
 end;
@@ -172,30 +220,40 @@ procedure TLayerAction.NeedSelectionLayerBackup;
 begin
   if not FBackupSelectionLayerDefined then
   begin
-    FBackupSelectionLayer := DuplicateBitmap(FImage.GetSelectionLayerIfExists);
+    FBackupSelectionLayer := DuplicateBitmap(CurrentState.SelectionLayer);
     FBackupSelectionLayerDefined := true;
   end;
 end;
 
-constructor TLayerAction.Create(AImage: TLazPaintImage);
+constructor TLayerAction.Create(AState: TImageState; AApplyOfsBefore: boolean);
+var
+  layerOfsDiff: TCustomImageDifference;
 begin
-  if AImage <> nil then
-  begin
-    if not AImage.CheckNoAction(True) then
-      raise exception.Create(rsConflictingActions);
-  end;
-  FImage := AImage;
-  FImage.ActionInProgress := self;
+  FImageState := AState;
   FBackupSelectedLayer := nil;
   FBackupSelection := nil;
   FBackupSelectionLayer := nil;
   FBackupSelectedLayerDefined := false;
-  FBackupSelectionDefined := false;
+  FBackupSelectionMaskDefined := false;
   FBackupSelectionLayerDefined := false;
-  FSelectedLayerChangedArea := EmptyRect;
+  FBackupSelectionTransform := CurrentState.SelectionTransform;
+  FSelectedImageLayerChangedArea := EmptyRect;
   FSelectionLayerChangedArea := EmptyRect;
-  FSelectionChangedArea := EmptyRect;
+  FSelectionMaskChangedArea := EmptyRect;
   FDone := false;
+  FPrediff := TComposedImageDifference.Create;
+  if AApplyOfsBefore then
+  begin
+    with CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex] do
+      layerOfsDiff := CurrentState.ComputeLayerOffsetDifference(X,Y);
+    if layerOfsDiff.IsIdentity then FreeAndNil(layerOfsDiff)
+    else
+    begin
+      layerOfsDiff.ApplyTo(CurrentState);
+      FPrediff.Add(layerOfsDiff);
+    end;
+  end;
+  if FPrediff.Count = 0 then FreeAndNil(FPrediff);
 end;
 
 destructor TLayerAction.Destroy;
@@ -204,67 +262,66 @@ begin
   FBackupSelectedLayer.Free;
   FBackupSelection.Free;
   FBackupSelectionLayer.Free;
-  if FImage <> nil then
-  begin
-    if FImage.ActionInProgress = self then
-      FImage.ActionInProgress := nil;
-  end;
+  if Assigned(FOnDestroy) then FOnDestroy(self);
   inherited Destroy;
 end;
 
-procedure TLayerAction.ReplaceDrawingLayer(bmp: TBGRABitmap; AOwned: boolean);
-begin
-  FImage.ReplaceDrawingLayer(bmp,AOwned);
-end;
-
 procedure TLayerAction.ReplaceCurrentSelection(AValue: TBGRABitmap);
 begin
-  NeedSelectionBackup;
-  if (AValue.Width = FImage.CurrentSelection.Width) and
-    (AValue.Height = FImage.CurrentSelection.Height) then
-    NotifyChange(FImage.CurrentSelection, AValue.GetDifferenceBounds(FImage.CurrentSelection))
+  if AValue = CurrentState.SelectionMask then exit;
+  NeedSelectionMaskBackup;
+  if Assigned(AValue) and Assigned(CurrentState.SelectionMask) and
+    (AValue.Width = CurrentState.SelectionMask.Width) and
+    (AValue.Height = CurrentState.SelectionMask.Height) then
+    NotifyChange(CurrentState.SelectionMask, AValue.GetDifferenceBounds(CurrentState.SelectionMask))
   else
   begin
-    NotifyChange(FImage.CurrentSelection, rect(0,0,FImage.CurrentSelection.Width,FImage.CurrentSelection.Height));
-    NotifyChange(AValue, rect(0,0,AValue.Width,AValue.Height));
+    if Assigned(CurrentState.SelectionMask) then
+      NotifyChange(CurrentState.SelectionMask, rect(0,0,CurrentState.SelectionMask.Width,CurrentState.SelectionMask.Height));
+    if Assigned(AValue) then
+      NotifyChange(AValue, rect(0,0,AValue.Width,AValue.Height));
   end;
-  FImage.ReplaceCurrentSelection(AValue);
+  CurrentState.SelectionMask.Free;
+  CurrentState.SelectionMask := AValue
 end;
 
 procedure TLayerAction.NotifyChange(ADest: TBGRABitmap; ARect: TRect);
 begin
   if ADest = nil then exit;
-  if not IntersectRect(ARect, ARect, rect(0,0,FImage.Width,FImage.Height)) then exit;
-  if ADest = FImage.CurrentSelection then
-    FSelectionChangedArea := RectUnion(FSelectionChangedArea, ARect)
-  else if ADest = FImage.GetSelectedImageLayer then
-    FSelectedLayerChangedArea := RectUnion(FSelectedLayerChangedArea, ARect)
-  else if ADest = FImage.GetSelectionLayerIfExists then
+  if not IntersectRect(ARect, ARect, rect(0,0,CurrentState.Width,CurrentState.Height)) then exit;
+  if ADest = CurrentState.SelectionMask then
+    FSelectionMaskChangedArea := RectUnion(FSelectionMaskChangedArea, ARect)
+  else if ADest = CurrentState.SelectedImageLayer then
+    FSelectedImageLayerChangedArea := RectUnion(FSelectedImageLayerChangedArea, ARect)
+  else if ADest = CurrentState.SelectionLayer then
     FSelectionLayerChangedArea := RectUnion(FSelectionLayerChangedArea, ARect);
+  if Assigned(FOnNotifyChange) then
+    FOnNotifyChange(self, ADest, ARect);
 end;
 
-procedure TLayerAction.RestoreSelection;
+procedure TLayerAction.RestoreSelectionMask;
 var prevClip: TRect;
 begin
-  if FBackupSelectionDefined then
+  if FBackupSelectionMaskDefined then
   begin
-    if not AllChangesNotified then FSelectionChangedArea := rect(0,0,FImage.Width,FImage.Height);
-    if IsRectEmpty(FSelectionChangedArea) then exit;
-    prevClip := FImage.CurrentSelection.ClipRect;
-    FImage.CurrentSelection.ClipRect := FSelectionChangedArea;
+    if not ChangeBoundsNotified then FSelectionMaskChangedArea := rect(0,0,CurrentState.Width,CurrentState.Height);
+    if IsRectEmpty(FSelectionMaskChangedArea) then exit;
+    prevClip := CurrentState.SelectionMask.ClipRect;
+    CurrentState.SelectionMask.ClipRect := FSelectionMaskChangedArea;
     if Assigned(FBackupSelection) then
-      FImage.CurrentSelection.PutImage(0,0,FBackupSelection,dmSet)
+      CurrentState.SelectionMask.PutImage(0,0,FBackupSelection,dmSet)
     else
-      FImage.CurrentSelection.FillRect(0,0,FImage.Width,FImage.Height,BGRABlack,dmSet);
-    FImage.CurrentSelection.ClipRect := prevClip;
-    FImage.SelectionMayChange(FSelectionChangedArea);
-    FSelectionChangedArea := EmptyRect;
+      CurrentState.SelectionMask.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRABlack,dmSet);
+    CurrentState.SelectionMask.ClipRect := prevClip;
+    If Assigned(FOnNotifyChange) then
+      FOnNotifyChange(self, CurrentState.SelectionMask, FSelectionMaskChangedArea);
+    FSelectionMaskChangedArea := EmptyRect;
   end;
 end;
 
 procedure TLayerAction.RestoreDrawingLayer;
 begin
-  if FImage.SelectionEmpty then RestoreSelectedLayer
+  if CurrentState.SelectionMaskEmpty then RestoreSelectedLayer
     else RestoreSelectionLayer;
 end;
 
@@ -273,35 +330,37 @@ var prevClip: TRect;
 begin
   if FBackupSelectedLayerDefined then
   begin
-    if not AllChangesNotified then FSelectedLayerChangedArea := rect(0,0,FImage.Width,FImage.Height);
-    if IsRectEmpty(FSelectedLayerChangedArea) then exit;
-    prevClip := FImage.GetSelectedImageLayer.ClipRect;
-    FImage.GetSelectedImageLayer.ClipRect := FSelectedLayerChangedArea;
+    if not ChangeBoundsNotified then FSelectedImageLayerChangedArea := rect(0,0,CurrentState.Width,CurrentState.Height);
+    if IsRectEmpty(FSelectedImageLayerChangedArea) then exit;
+    prevClip := CurrentState.SelectedImageLayer.ClipRect;
+    CurrentState.SelectedImageLayer.ClipRect := FSelectedImageLayerChangedArea;
     if Assigned(FBackupSelectedLayer) then
-      FImage.GetSelectedImageLayer.PutImage(0,0,FBackupSelectedLayer,dmSet)
+      CurrentState.SelectedImageLayer.PutImage(0,0,FBackupSelectedLayer,dmSet)
     else
-      FImage.GetSelectedImageLayer.FillRect(0,0,FImage.Width,FImage.Height,BGRAPixelTransparent,dmSet);
-    FImage.GetSelectedImageLayer.ClipRect := prevClip;
-    FImage.LayerMayChange(FImage.GetSelectedImageLayer,FSelectedLayerChangedArea);
-    FSelectedLayerChangedArea := EmptyRect;
+      CurrentState.SelectedImageLayer.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRAPixelTransparent,dmSet);
+    CurrentState.SelectedImageLayer.ClipRect := prevClip;
+    If Assigned(FOnNotifyChange) then
+      FOnNotifyChange(self, CurrentState.SelectedImageLayer, FSelectedImageLayerChangedArea);
+    FSelectedImageLayerChangedArea := EmptyRect;
   end;
 end;
 
 procedure TLayerAction.RestoreSelectionLayer;
 var prevClip: TRect;
 begin
-  if FBackupSelectionLayerDefined and (GetSelectionLayerIfExists <> nil) then
+  if FBackupSelectionLayerDefined and (CurrentState.SelectionLayer <> nil) then
   begin
-    if not AllChangesNotified then FSelectionLayerChangedArea := rect(0,0,FImage.Width,FImage.Height);
+    if not ChangeBoundsNotified then FSelectionLayerChangedArea := rect(0,0,CurrentState.Width,CurrentState.Height);
     if IsRectEmpty(FSelectionLayerChangedArea) then exit;
-    prevClip := FImage.GetSelectionLayerIfExists.ClipRect;
-    FImage.GetSelectionLayerIfExists.ClipRect := FSelectionLayerChangedArea;
+    prevClip := CurrentState.SelectionLayer.ClipRect;
+    CurrentState.SelectionLayer.ClipRect := FSelectionLayerChangedArea;
     if Assigned(FBackupSelectionLayer) then
-      FImage.GetSelectionLayerIfExists.PutImage(0,0,FBackupSelectionLayer,dmSet)
+      CurrentState.SelectionLayer.PutImage(0,0,FBackupSelectionLayer,dmSet)
     else
-      FImage.GetSelectionLayerIfExists.FillRect(0,0,FImage.Width,FImage.Height,BGRAPixelTransparent,dmSet);
-    FImage.GetSelectionLayerIfExists.ClipRect := prevClip;
-    FImage.LayerMayChange(FImage.GetSelectionLayerIfExists,FSelectionLayerChangedArea);
+      CurrentState.SelectionLayer.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRAPixelTransparent,dmSet);
+    CurrentState.SelectionLayer.ClipRect := prevClip;
+    If Assigned(FOnNotifyChange) then
+      FOnNotifyChange(self, CurrentState.SelectionLayer, FSelectionLayerChangedArea);
     FSelectionLayerChangedArea := EmptyRect;
   end;
 end;
@@ -314,76 +373,131 @@ end;
 
 procedure TLayerAction.QuerySelection;
 begin
-  NeedSelectionBackup;
-  FImage.QuerySelection;
+  NeedSelectionMaskBackup;
+  CurrentState.QuerySelectionMask;
 end;
 
 procedure TLayerAction.RemoveSelection;
+var bounds: TRect;
 begin
-  if not FImage.SelectionEmpty or (FImage.GetSelectionLayerIfExists <> nil) then
+  if not CurrentState.SelectionMaskEmpty or (CurrentState.SelectionLayer <> nil) then
   begin
-    NeedSelectionBackup;
+    NeedSelectionMaskBackup;
     NeedSelectionLayerBackup;
-    FImage.RemoveSelection;
+    bounds := CurrentState.GetTransformedSelectionMaskBounds;
+    NotifyChange(CurrentState.SelectionLayer, bounds);
+    CurrentState.RemoveSelection;
   end;
 end;
 
 procedure TLayerAction.EraseSelectionInBitmap;
+var offs: TPoint;
+  r: TRect;
 begin
-  if not FImage.SelectionEmpty then
+  if not CurrentState.SelectionMaskEmpty then
   begin
     NeedSelectedLayerBackup;
-    FImage.EraseSelectionInBitmap;
+    offs := CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex];
+    r := CurrentState.GetSelectionMaskBounds;
+    SubstractMask(GetSelectedImageLayer,-offs.X+r.left,-offs.Y+r.top,CurrentState.SelectionMask,r);
+    OffsetRect(r,-offs.x,-offs.y);
+    NotifyChange(GetSelectedImageLayer,r);
+  end;
+end;
+
+procedure TLayerAction.MergeWithSelection(AApplyMask: boolean);
+var offs: TPoint;
+  sourceRect,destRect: TRect;
+begin
+  if not CurrentState.SelectionLayerEmpty and not (AApplyMask and CurrentState.SelectionMaskEmpty) then
+  begin
+    sourceRect := CurrentState.GetSelectionLayerBounds;
+    if AApplyMask then
+    begin
+      CurrentState.SelectionLayer.ApplyMask(CurrentState.SelectionMask,CurrentState.GetSelectionLayerBounds);
+      IntersectRect(sourceRect,sourceRect,CurrentState.GetSelectionMaskBounds);
+      NotifyChange(CurrentState.SelectionLayer,CurrentState.GetSelectionLayerBounds);
+    end;
+    offs := CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex];
+    destRect := sourceRect;
+    OffsetRect(destRect, -offs.x,-offs.y);
+    GetSelectedImageLayer.PutImagePart(destRect.left,destRect.top,CurrentState.SelectionLayer,sourceRect,dmDrawWithTransparency);
+    NotifyChange(GetSelectedImageLayer,destRect);
+    CurrentState.ReplaceSelectionLayer(nil,true);
   end;
 end;
 
 procedure TLayerAction.ReleaseSelection;
 var bounds: TRect;
 begin
-  if not FImage.SelectionEmpty then
+  if not CurrentState.SelectionMaskEmpty then
   begin
-    bounds := FImage.SelectionBounds;
-    NeedSelectionBackup;
-    NotifyChange(FImage.CurrentSelection, bounds);
-    if FImage.GetSelectionLayerIfExists <> nil then
+    bounds := CurrentState.GetSelectionMaskBounds;
+    NeedSelectionMaskBackup;
+    NotifyChange(CurrentState.SelectionMask, bounds);
+    if CurrentState.SelectionLayer <> nil then
     begin
       NeedSelectedLayerBackup;
-      NotifyChange(FImage.GetSelectedImageLayer, bounds);
+      NotifyChange(CurrentState.SelectedImageLayer, bounds);
       NeedSelectionLayerBackup;
-      NotifyChange(FImage.GetSelectionLayerIfExists, bounds);
+      NotifyChange(CurrentState.SelectionLayer, bounds);
     end;
-    FImage.ReleaseSelection;
+
+    ApplySelectionMask;
+    CurrentState.SelectionMask.Free;
+    CurrentState.SelectionMask := nil;
+    ApplySelectionTransform(False);
+    MergeWithSelection(False);
   end;
 end;
 
 procedure TLayerAction.RetrieveSelection;
+var temp : TBGRABitmap;
+  offs: TPoint;
+  r, maskBounds: TRect;
 begin
-  if not FImage.SelectionEmpty then
+  if not CurrentState.SelectionMaskEmpty then
   begin
     NeedSelectedLayerBackup;
     NeedSelectionLayerBackup;
-    FImage.RetrieveSelection;
+    ApplySelectionTransform;
+    MergeWithSelection;
+    offs := CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex];
+    maskBounds := CurrentState.GetSelectionMaskBounds;
+    r := maskBounds;
+    OffsetRect(r, -offs.x, -offs.y);
+    IntersectRect(r, r, rect(0,0,GetSelectedImageLayer.Width,GetSelectedImageLayer.Height));
+    temp := TBGRABitmap.Create(CurrentState.Width,CurrentState.Height);
+    temp.PutImagePart(r.left+offs.x,r.top+offs.y,GetSelectedImageLayer,r,dmSet);
+    temp.ApplyMask(CurrentState.SelectionMask,maskBounds);
+    BGRAReplace(CurrentState.SelectionLayer,temp);
+    NotifyChange(CurrentState.SelectionLayer,maskBounds);
   end;
 end;
 
-function TLayerAction.RetrieveSelectionIfLayerEmpty(removeFromBitmap: boolean
-  ): boolean;
+function TLayerAction.RetrieveSelectionIfLayerEmpty(removeFromBitmap: boolean): boolean;
 begin
   NeedSelectedLayerBackup;
   NeedSelectionLayerBackup;
-  result := FImage.RetrieveSelectionIfLayerEmpty(removeFromBitmap);
+  if CurrentState.SelectionLayerEmpty then
+  begin
+    RetrieveSelection;
+    if removeFromBitmap then EraseSelectionInBitmap;
+    result := true;
+  end
+  else result := false;
 end;
 
 function TLayerAction.GetOrCreateSelectionLayer: TBGRABitmap;
 begin
   NeedSelectionLayerBackup;
-  result := FImage.GetOrCreateSelectionLayer;
+  result := CurrentState.GetOrCreateSelectionLayer;
 end;
 
 function TLayerAction.GetSelectionLayerIfExists: TBGRABitmap;
 begin
   NeedSelectionLayerBackup;
-  result := FImage.GetSelectionLayerIfExists;
+  result := CurrentState.SelectionLayer;
 end;
 
 procedure TLayerAction.ReplaceSelectionLayer(bmp: TBGRABitmap; AOwned: boolean);
@@ -396,38 +510,61 @@ begin
     NotifyChange(dest, bmp.GetDifferenceBounds(dest))
   else
   begin
-    if dest <> nil then NotifyChange(dest, rect(0,0,dest.Width,dest.Height));
-    if bmp <> nil then NotifyChange(bmp, rect(0,0,bmp.Width,bmp.Height));
+    if dest <> nil then NotifyChange(dest, dest.GetImageBounds);
+    if bmp <> nil then NotifyChange(bmp, bmp.GetImageBounds);
   end;
-  FImage.ReplaceSelectionLayer(bmp,AOwned);
+  CurrentState.ReplaceSelectionLayer(bmp,AOwned);
 end;
 
 procedure TLayerAction.ApplySelectionTransform(ApplyToMask: boolean);
+var
+  newBmp: TBGRABitmap;
+  newLeft, newTop: integer;
+  r: TRect;
 begin
-  NeedSelectionLayerBackup;
-  FImage.ApplySelectionTransform(ApplyToMask);
+  if not IsAffineMatrixIdentity(CurrentState.SelectionTransform) then
+  begin
+    if ApplyToMask and not CurrentState.SelectionMaskEmpty then
+    begin
+      NeedSelectionMaskBackup;
+      CurrentState.ComputeTransformedSelectionMask(newBmp,newLeft,newTop);
+      r := CurrentState.GetSelectionMaskBounds;
+      CurrentState.SelectionMask.FillRect(r, BGRABlack, dmSet);
+      NotifyChange(CurrentState.SelectionMask, r);
+      CurrentState.SelectionMask.PutImage(newLeft,newTop,newBmp,dmSet);
+      newBmp.Free;
+      CurrentState.DiscardSelectionMaskBounds;
+    end;
+    if not CurrentState.SelectionLayerEmpty then
+    begin
+      NeedSelectionLayerBackup;
+      CurrentState.ComputeTransformedSelectionLayer(newBmp,newLeft,newTop);
+      r := CurrentState.GetSelectionLayerBounds;
+      CurrentState.SelectionLayer.FillRect(r, BGRAPixelTransparent, dmSet);
+      NotifyChange(CurrentState.SelectionLayer, r);
+      CurrentState.SelectionLayer.PutImage(newLeft,newTop,newBmp,dmSet);
+      newBmp.Free;
+      CurrentState.DiscardSelectionLayerBounds;
+    end;
+    CurrentState.SelectionTransform := AffineMatrixIdentity;
+    NotifyChange(CurrentState.SelectionMask, CurrentState.GetSelectionMaskBounds);
+    NotifyChange(CurrentState.SelectionLayer, CurrentState.GetSelectionLayerBounds);
+  end;
 end;
 
 procedure TLayerAction.ApplySelectionMask;
+var r: TRect;
 begin
   NeedSelectionLayerBackup;
-  FImage.ApplySelectionMask;
-end;
-
-procedure TLayerAction.ReplaceSelectedLayer(AValue: TBGRABitmap; AOwned: boolean);
-var dest: TBGRABitmap;
-begin
-  NeedSelectedLayerBackup;
-  dest := GetSelectedImageLayer;
-  if (dest <> nil) and (AValue.Width = dest.Width) and
-    (AValue.Height = dest.Height) then
-    NotifyChange(dest, AValue.GetDifferenceBounds(dest))
-  else
+  if (CurrentState.SelectionMask <> nil) and (CurrentState.SelectionLayer <> nil) then
   begin
-    if dest <> nil then NotifyChange(dest, rect(0,0,dest.Width,dest.Height));
-    NotifyChange(AValue, rect(0,0,AValue.Width,AValue.Height));
+    r := GetSelectionLayerBounds;
+    if not IsRectEmpty(r) then
+    begin
+      GetOrCreateSelectionLayer.ApplyMask(CurrentState.SelectionMask,r);
+      NotifyChange(CurrentState.GetOrCreateSelectionLayer,r);
+    end;
   end;
-  FImage.ReplaceSelectedLayer(AValue, AOwned);
 end;
 
 procedure TLayerAction.Validate;
@@ -441,41 +578,63 @@ procedure TLayerAction.PartialCancel;
 begin
   RestoreSelectedLayer;
   RestoreSelectionLayer;
-  RestoreSelection;
+  RestoreSelectionMask;
 end;
 
 procedure TLayerAction.PartialValidate(ADiscardBackup: boolean = false);
-begin
-  if FBackupSelectedLayerDefined or FBackupSelectionDefined or FBackupSelectionLayerDefined then
+var
+  prevLayerOriginalMatrix: TAffineMatrix;
+  prevLayerOriginaData: TStream;
+  imgDiff: TImageLayerStateDifference;
+  composedDiff: TComposedImageDifference;
+  ofs: TPoint;
+  applyOfs: TCustomImageDifference;
+  appendOfs, owned: boolean;
+begin
+  if FBackupSelectedLayerDefined or FBackupSelectionMaskDefined or FBackupSelectionLayerDefined then
   begin
-    if AllChangesNotified then
-      if IsRectEmpty(FSelectedLayerChangedArea) and IsRectEmpty(FSelectionChangedArea) and
+    if ChangeBoundsNotified then
+      if IsRectEmpty(FSelectedImageLayerChangedArea) and IsRectEmpty(FSelectionMaskChangedArea) and
          IsRectEmpty(FSelectionLayerChangedArea) then exit;
     if FBackupSelectionLayerDefined then
     begin
-      FImage.DiscardSelectionLayerBounds;
-      if FImage.SelectionLayerIsEmpty then
-        FImage.ReplaceSelectionLayer(nil,True);
+      CurrentState.DiscardSelectionLayerBounds;
+      if CurrentState.SelectionLayerEmpty then
+        CurrentState.ReplaceSelectionLayer(nil,True);
     end;
-    if FBackupSelectionDefined then
+    if FBackupSelectionMaskDefined then
     begin
-      FImage.DiscardSelectionBounds;
-      if FImage.SelectionEmpty then
-      begin
-        FImage.ReplaceSelectionLayer(nil,true);
-        FImage.ReplaceCurrentSelection(nil);
-      end;
+      CurrentState.DiscardSelectionMaskBounds;
+      if CurrentState.SelectionMaskEmpty then
+        CurrentState.RemoveSelection;
+    end;
+    //original will be backed up if there are changes in the raster image of the selected layer
+    if CurrentState.LayerOriginalDefined[CurrentState.SelectedImageLayerIndex] and
+       (FBackupSelectedLayerDefined or not IsRectEmpty(FSelectedImageLayerChangedArea)) then
+    begin
+      prevLayerOriginaData:= TMemoryStream.Create;
+      CurrentState.SaveOriginalToStream(prevLayerOriginaData);
+      prevLayerOriginalMatrix:= CurrentState.LayerOriginalMatrix[CurrentState.SelectedImageLayerIndex];
+      CurrentState.DiscardOriginal(false);
+    end else
+    begin
+      prevLayerOriginaData := nil;
+      prevLayerOriginalMatrix:= AffineMatrixIdentity;
     end;
-    if AllChangesNotified then
-      FImage.AddLayerUndo(FBackupSelectedLayer, FSelectedLayerChangedArea,
-        FBackupSelection, FSelectionChangedArea,
-        FBackupSelectionLayer, FSelectionLayerChangedArea)
-      else
-      FImage.AddLayerUndo(FBackupSelectedLayer, FBackupSelectedLayerDefined,
-        FBackupSelection, FBackupSelectionDefined,
-        FBackupSelectionLayer, FBackupSelectionLayerDefined);
 
-    FImage.OnImageChanged.NotifyObservers;
+    if ChangeBoundsNotified then
+      imgDiff := CurrentState.ComputeLayerDifference(FBackupSelectedLayer, FSelectedImageLayerChangedArea,
+        FBackupSelection, FSelectionMaskChangedArea,
+        FBackupSelectionLayer, FSelectionLayerChangedArea,
+        FBackupSelectionTransform,
+        prevLayerOriginaData, prevLayerOriginalMatrix) as TImageLayerStateDifference
+    else
+      imgDiff := CurrentState.ComputeLayerDifference(FBackupSelectedLayer, FBackupSelectedLayerDefined,
+        FBackupSelection, FBackupSelectionMaskDefined,
+        FBackupSelectionLayer, FBackupSelectionLayerDefined,
+        FBackupSelectionTransform,
+        prevLayerOriginaData, prevLayerOriginalMatrix) as TImageLayerStateDifference;
+    if imgDiff.IsIdentity then FreeAndNil(imgDiff);
 
     if ADiscardBackup then
     begin
@@ -484,55 +643,112 @@ begin
       FreeAndNil(FBackupSelection);
       FBackupSelectedLayerDefined := false;
       FBackupSelectedLayerDefined := false;
-      FBackupSelectionDefined := false;
+      FBackupSelectionMaskDefined := false;
+
+      appendOfs:= Assigned(imgDiff) and imgDiff.ChangeImageLayer;
     end else
     begin
       if FBackupSelectionLayerDefined then
       begin
-        if (FBackupSelectionLayer = nil) and (FImage.GetSelectionLayerIfExists <> nil) then
+        if (FBackupSelectionLayer = nil) and (CurrentState.SelectionLayer <> nil) then
         begin
-          if not FImage.SelectionLayerIsEmpty then
-            FBackupSelectionLayer := Fimage.GetSelectionLayerIfExists.Duplicate as TBGRABitmap;
+          if not CurrentState.SelectionLayerEmpty then
+            FBackupSelectionLayer := CurrentState.SelectionLayer.Duplicate as TBGRABitmap;
         end else
         if Assigned(FBackupSelectionLayer) then
         begin
-          if AllChangesNotified then FBackupSelectionLayer.ClipRect := FSelectionLayerChangedArea;
-          if Assigned(FImage.GetSelectionLayerIfExists) then
-            FBackupSelectionLayer.PutImage(0,0,FImage.GetSelectionLayerIfExists,dmSet)
+          if ChangeBoundsNotified then FBackupSelectionLayer.ClipRect := FSelectionLayerChangedArea;
+          if Assigned(CurrentState.SelectionLayer) then
+            FBackupSelectionLayer.PutImage(0,0,CurrentState.SelectionLayer,dmSet)
           else
-            FBackupSelectionLayer.FillRect(0,0,FImage.Width,FImage.Height,BGRAPixelTransparent,dmSet);
+            FBackupSelectionLayer.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRAPixelTransparent,dmSet);
           FBackupSelectionLayer.NoClip;
         end;
         FSelectionLayerChangedArea := EmptyRect;
       end;
       if FBackupSelectedLayerDefined then
       begin
-        if AllChangesNotified then FBackupSelectedLayer.ClipRect := FSelectedLayerChangedArea;
-        if Assigned(FImage.GetSelectedImageLayer) then
-          FBackupSelectedLayer.PutImage(0,0,FImage.GetSelectedImageLayer,dmSet)
+        if ChangeBoundsNotified then FBackupSelectedLayer.ClipRect := FSelectedImageLayerChangedArea;
+        if Assigned(CurrentState.SelectedImageLayer) then
+          FBackupSelectedLayer.PutImage(0,0,CurrentState.SelectedImageLayer,dmSet)
         else
-          FBackupSelectedLayer.FillRect(0,0,FImage.Width,FImage.Height,BGRAPixelTransparent,dmSet);
+          FBackupSelectedLayer.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRAPixelTransparent,dmSet);
         FBackupSelectedLayer.NoClip;
-        FSelectedLayerChangedArea := EmptyRect;
+        FSelectedImageLayerChangedArea := EmptyRect;
       end;
-      if FBackupSelectionDefined then
+      if FBackupSelectionMaskDefined then
       begin
-        if (FBackupSelection = nil) and (FImage.CurrentSelection <> nil) then
+        if (FBackupSelection = nil) and (CurrentState.SelectionMask <> nil) then
         begin
-          if not FImage.SelectionEmpty then
-            FBackupSelection := Fimage.CurrentSelection.Duplicate as TBGRABitmap;
+          if not CurrentState.SelectionMaskEmpty then
+            FBackupSelection := CurrentState.SelectionMask.Duplicate as TBGRABitmap;
         end else
         if (FBackupSelection <> nil) then
         begin
-          if AllChangesNotified then FBackupSelection.ClipRect := FSelectionChangedArea;
-          if Assigned(FImage.CurrentSelection) then
-            FBackupSelection.PutImage(0,0,FImage.CurrentSelection,dmSet)
+          if ChangeBoundsNotified then FBackupSelection.ClipRect := FSelectionMaskChangedArea;
+          if Assigned(CurrentState.SelectionMask) then
+            FBackupSelection.PutImage(0,0,CurrentState.SelectionMask,dmSet)
           else
-            FBackupSelection.FillRect(0,0,FImage.Width,FImage.Height,BGRABlack,dmSet);
+            FBackupSelection.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRABlack,dmSet);
           FBackupSelection.NoClip;
         end;
-        FSelectionChangedArea := EmptyRect;
+        FSelectionMaskChangedArea := EmptyRect;
+      end;
+
+      appendOfs := false;
+    end;
+
+    if assigned(imgDiff) then
+    begin
+      if appendOfs or Assigned(FPrediff) then
+      begin
+        composedDiff := TComposedImageDifference.Create;
+        if Assigned(FPrediff) then
+        begin
+          composedDiff.AddRange(FPrediff);
+          FPrediff := nil;
+        end;
+        composedDiff.Add(imgDiff);
+        if appendOfs then
+        begin
+          ofs := CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex];
+          applyOfs:= CurrentState.ComputeLayerOffsetDifference(ofs.x, ofs.y);
+          if not applyOfs.IsIdentity then
+          begin
+            composedDiff.Add(applyOfs);
+            applyOfs.ApplyTo(CurrentState);
+          end else
+            applyOfs.Free;
+        end;
+        if Assigned(FOnNotifyUndo) then
+        begin
+          owned := false;
+          FOnNotifyUndo(self, composedDiff, owned);
+          if not owned then composedDiff.Free;
+        end;
+      end else
+      begin
+        if Assigned(FOnNotifyUndo) then
+        begin
+          owned := false;
+          FOnNotifyUndo(self, imgDiff, owned);
+          if not owned then imgDiff.Free;
+        end;
+      end;
+    end;
+
+    FBackupSelectionTransform := CurrentState.SelectionTransform;
+  end else
+  begin
+    if Assigned(FPrediff) then
+    begin
+      if Assigned(FOnNotifyUndo) then
+      begin
+        owned := false;
+        FOnNotifyUndo(self, FPrediff, owned);
+        if not owned then FPrediff.Free;
       end;
+      FPrediff := nil;
     end;
   end;
 end;

+ 106 - 29
lazpaint/ulayerstack.pas

@@ -96,13 +96,16 @@ type
 
 implementation
 
-uses BGRAFillInfo,uscaledpi,uresourcestrings,ublendop, uimage, utool, BGRAText, BGRAThumbnail;
+uses BGRAFillInfo,uscaledpi,uresourcestrings,ublendop, uimage, utool, BGRAText, BGRAThumbnail,
+   BGRALayerOriginal, math, BGRATransform, BGRASVGOriginal;
 
 function TFLayerStack.DrawLayerItem(ABitmap: TBGRABitmap; layerPos: TPoint; layerIndex: integer; ASelected: boolean): TDrawLayerItemResult;
-var LayerBmp: TBGRABitmap;
-    lColor,lColorTransp: TBGRAPixel;
-    barwidth: integer;
-    sourceCoords: Array Of TPointF;
+var
+  lColor,lColorTransp: TBGRAPixel;
+  barwidth: integer;
+  sourceCoords: Array Of TPointF;
+  reduced: TBGRABitmap;
+  reducedBounds: TRect;
 begin
   if ASelected then
     lColor := ColorToBGRA(ColorToRGB(clHighlightText))
@@ -115,6 +118,16 @@ begin
      pointf(layerPos.X+0.9*LayerRectWidth,layerPos.Y+round(LayerRectHeight*0.1)),
     pointf(layerPos.X+0.7*LayerRectWidth,layerPos.Y+round(LayerRectHeight*0.9)),
     pointf(layerPos.X+0.05*LayerRectWidth,layerPos.Y+round(LayerRectHeight*0.9))]);
+  reduced := TBGRABitmap.Create(round(LayerRectWidth*0.65), round(LayerRectHeight*0.8));
+  reducedBounds := RectWithSize(LazPaintInstance.Image.LayerOffset[layerIndex].X,
+                        LazPaintInstance.Image.LayerOffset[layerIndex].Y,
+                        LazPaintInstance.Image.LayerBitmap[layerIndex].Width,
+                        LazPaintInstance.Image.LayerBitmap[layerIndex].Height);
+  reducedBounds.Left := round(reducedBounds.Left*reduced.Width/LazPaintInstance.Image.Width);
+  reducedBounds.Top := round(reducedBounds.Top*reduced.Height/LazPaintInstance.Image.Height);
+  reducedBounds.Right := round(reducedBounds.Right*reduced.Width/LazPaintInstance.Image.Width);
+  reducedBounds.Bottom := round(reducedBounds.Bottom*reduced.Height/LazPaintInstance.Image.Height);
+  reduced.StretchPutImage(reducedBounds, LazPaintInstance.Image.LayerBitmap[layerIndex], dmDrawWithTransparency);
 
   result.PreviewPts[0].y += 0.5;
   result.PreviewPts[1].y += 0.5;
@@ -125,10 +138,11 @@ begin
   else
     ABitmap.FillPolyAntialias(result.PreviewPts,background);
 
-  layerBmp := LazPaintInstance.Image.LayerBitmap[layerIndex];
-  sourceCoords := PointsF([pointf(-0.49,-0.49),pointf(layerBmp.Width-0.51,-0.49),
-            pointf(layerBmp.Width-0.51,layerBmp.Height-0.51),pointf(-0.49,layerBmp.Height-0.51)]);
-  ABitmap.FillPolyLinearMapping( result.PreviewPts, layerBmp, sourceCoords, False);
+  sourceCoords := PointsF([pointf(-0.49,-0.49),pointf(reduced.Width-0.51,-0.49),
+            pointf(reduced.Width-0.51,reduced.Height-0.51),pointf(-0.49,reduced.Height-0.51)]);
+  ABitmap.FillPolyLinearMapping(result.PreviewPts, reduced, sourceCoords, False);
+  reduced.Free;
+
   result.PreviewPts[0].y -= 0.5;
   result.PreviewPts[1].y -= 0.5;
   result.PreviewPts[2].y += 0.5;
@@ -309,14 +323,14 @@ begin
   if i < LazPaintInstance.Image.NbLayers then
   begin
     if not LazPaintInstance.Image.SelectionLayerIsEmpty and
-        (i <> LazPaintInstance.Image.currentImageLayerIndex) then
+        (i <> LazPaintInstance.Image.CurrentLayerIndex) then
     begin
       topmostInfo := LazPaintInstance.HideTopmost;
       res := MessageDlg(rsTransferSelectionToOtherLayer,mtConfirmation,[mbOk,mbCancel],0);
       LazPaintInstance.ShowTopmost(topmostInfo);
       if res = mrOk then
       begin
-        if LazPaintInstance.Image.SetCurrentImageLayerIndex(i) then
+        if LazPaintInstance.Image.SetCurrentLayerByIndex(i) then
         begin
           renaming := false;
           BGRALayerStack.RedrawBitmap;
@@ -324,7 +338,7 @@ begin
       end;
       exit;
     end;
-    if LazPaintInstance.Image.SetCurrentImageLayerIndex(i) then
+    if LazPaintInstance.Image.SetCurrentLayerByIndex(i) then
     begin
       renaming := false;
       movingItemStart := true;
@@ -356,7 +370,7 @@ begin
       str := BlendOperationStr[BlendOperation[i]];
       if blendOps.IndexOf(str) = -1 then
         blendOps.Add(str);
-      if i = LazPaintInstance.Image.currentImageLayerIndex then
+      if i = LazPaintInstance.Image.CurrentLayerIndex then
         selectedStr := str;
     end;
   if selectedStr = BlendOperationStr[boTransparent] then
@@ -399,10 +413,10 @@ begin
 
   InterruptorWidth := LayerRectHeight div 4;
   InterruptorHeight := LayerRectHeight div 4;
-  temp := ScaleY(20,OriginalDPI);
+  temp := ScaleY(28,OriginalDPI);
   if InterruptorWidth > temp then InterruptorWidth := temp;
   if InterruptorHeight > temp then InterruptorHeight := temp;
-  temp := ScaleY(10,OriginalDPI);
+  temp := ScaleY(7,OriginalDPI);
   if InterruptorHeight < temp then InterruptorHeight := temp;
   if InterruptorWidth < temp then InterruptorWidth := temp;
   StackWidth := InterruptorWidth+LayerRectWidth+ABitmap.TextSize('Some layer name').cx;
@@ -469,7 +483,7 @@ begin
   if ScrollStackItemIntoView then
   begin
     ScrollPos.X := 0;
-    ScrollPos.Y := (LazPaintInstance.Image.NbLayers-1-LazPaintInstance.Image.currentImageLayerIndex)*LayerRectHeight;
+    ScrollPos.Y := (LazPaintInstance.Image.NbLayers-1-LazPaintInstance.Image.CurrentLayerIndex)*LayerRectHeight;
     ScrollStackItemIntoView := false;
   end;
 
@@ -532,8 +546,56 @@ var i: integer;
   layerPos: TPoint;
   lSelected: boolean;
   y: integer;
-  clipping: TRect;
-  lColor: TBGRAPixel;
+  clipping, rKind: TRect;
+  lColor, lColorTrans: TBGRAPixel;
+
+  procedure DrawKind(AClass: TBGRALayerOriginalAny);
+  var
+    eb: TEasyBezierCurve;
+    w: single;
+    i: integer;
+    m: TAffineMatrix;
+  begin
+    if AClass = TBGRALayerImageOriginal then
+    begin
+      Bitmap.Rectangle(rKind, lColor,lColorTrans, dmDrawWithTransparency);
+      Bitmap.HorizLine(rKind.Left+1,rKind.Top+(rKind.Height-1) div 2,rKind.Right-2, lColor, dmDrawWithTransparency);
+      Bitmap.VertLine(rKind.Left+(rKind.Width-1) div 2,rKind.Top+1,rKind.Bottom-2, lColor, dmDrawWithTransparency);
+    end else
+    if AClass = TBGRALayerSVGOriginal then
+    begin
+      m := AffineMatrixTranslation(rKind.Left,rKind.Top+rKind.Height*0.1)*AffineMatrixScale(rKind.Width,rKind.Height*0.8);
+      w := max(1,rKind.Height/10);
+      eb := EasyBezierCurve([PointF(0.28,0),PointF(0,0),PointF(0,0.5),PointF(0.28,0.5),PointF(1/3,1),PointF(0,1)],False,cmCurve);
+      for i := 0 to eb.PointCount-1 do eb.Point[i] := m*eb.Point[i];
+      Bitmap.DrawPolyLineAntialias(eb.ToPoints, lColor, w, true);
+      eb := EasyBezierCurve([PointF(0.33,0),PointF(0.47,1),PointF(0.6,0)],False,cmAngle);
+      for i := 0 to eb.PointCount-1 do eb.Point[i] := m*eb.Point[i];
+      Bitmap.DrawPolyLineAntialias(eb.ToPoints, lColor, w, true);
+      eb := EasyBezierCurve([PointF(1,0),PointF(0.7,0),PointF(2/3,1),PointF(1,1),PointF(1,0.5),PointF(5/6,0.5)],False,cmCurve);
+      eb.CurveMode[eb.PointCount-2] := cmAngle;
+      for i := 0 to eb.PointCount-1 do eb.Point[i] := m*eb.Point[i];
+      Bitmap.DrawPolyLineAntialias(eb.ToPoints, lColor, w, true);
+    end else
+    if AClass = nil then
+    begin
+      eb := EasyBezierCurve([PointF(0.25,0.25),PointF(0.32,0.07),PointF(0.5,0),PointF(0.68,0.07),PointF(0.75,0.20),
+                             PointF(0.75,0.30),PointF(0.70,0.40),PointF(0.5,0.5),PointF(0.5,0.70)],False,cmCurve);
+      m := AffineMatrixTranslation(rKind.Left,rKind.Top)*AffineMatrixScale(rKind.Width,rKind.Height);
+      for i := 0 to eb.PointCount-1 do eb.Point[i] := m*eb.Point[i];
+      w := max(1,rKind.Height/10);
+      Bitmap.DrawPolyLineAntialias(eb.ToPoints, lColor, w, true);
+      Bitmap.FillEllipseAntialias((rKind.Left+rKind.Right)/2, rKind.Bottom - 1 - (w-1)/2, w*0.6,w*0.6, lColor);
+    end else
+    begin
+      Bitmap.EllipseAntialias(rKind.Left+rKind.Width / 3, rKind.Top+rKind.Height / 3,rKind.Width / 3,rKind.Height / 3,
+                              lColor, 1, lColorTrans);
+      Bitmap.DrawPolygonAntialias([PointF(rKind.Left+rKind.Width/4,rKind.Bottom),
+                                   PointF(rKind.Left+rKind.Width/2,rKind.Top+rKind.Height/4),
+                                   PointF(rKind.Right,rKind.Bottom)],lColor,1, lColorTrans);
+    end;
+  end;
+
 begin
   if Layout then
   begin
@@ -551,7 +613,7 @@ begin
     begin
       with LazPaintInstance.Image do
       begin
-        if i = currentImageLayerIndex then
+        if i = CurrentLayerIndex then
         begin
           Bitmap.FillRect(layerPos.X,layerPos.Y,layerPos.X+StackWidth,layerPos.Y+LayerRectHeight,ColorToBGRA(ColorToRGB(clHighlight)),dmSet);
           lSelected:= true;
@@ -563,8 +625,8 @@ begin
         end;
         if UpdateItem <> -1 then clipping := rect(layerPos.X,layerPos.Y,layerPos.X+StackWidth,layerPos.Y+LayerRectHeight);
 
-        interruptors[i] := rect(layerPos.X+InterruptorWidth div 5,layerpos.Y+(LayerRectHeight-InterruptorHeight) div 2,layerPos.X+InterruptorWidth,
-           layerpos.Y+(LayerRectHeight-InterruptorHeight) div 2+InterruptorHeight);
+        interruptors[i] := RectWithSize(layerPos.X+InterruptorWidth div 5,layerpos.Y+(LayerRectHeight-5*InterruptorHeight div 2) div 2,
+                                        InterruptorWidth, InterruptorHeight);
 
         if (layerpos.Y+LayerRectHeight > 0) and (layerpos.Y < Bitmap.Height) then
         begin
@@ -573,6 +635,9 @@ begin
           else
             lColor := ColorToBGRA(ColorToRGB(clWindowText));
 
+          lColorTrans := lColor;
+          lColorTrans.alpha := lColorTrans.alpha div 3;
+
           Bitmap.Rectangle(interruptors[i],lColor,dmDrawWithTransparency);
           if LayerVisible[i] then
           with interruptors[i] do
@@ -586,6 +651,18 @@ begin
                   PointF(right-2,top-2))]),lColor,1.5);
           end;
 
+          rKind := interruptors[i];
+          rKind.Offset(0, InterruptorHeight*3 div 2);
+          if LayerOriginalDefined[i] then
+          begin
+            if LayerOriginalKnown[i] then
+              DrawKind(LayerOriginalClass[i])
+            else
+              DrawKind(nil);
+          end
+          else
+            DrawKind(TBGRALayerImageOriginal);
+
           inc(layerPos.X,InterruptorWidth);
           if movingItemStart and (i= movingItemSourceIndex) then
           begin
@@ -682,20 +759,20 @@ var blendOp: TBlendOperation;
 begin
   blendOp := boTransparent;
   topmostInfo := LazPaintInstance.HideTopmost;
-  if LazPaintInstance.Image.currentImageLayerIndex > 0 then
-    tempUnder := LazPaintInstance.Image.ComputeFlatImage(0,LazPaintInstance.Image.currentImageLayerIndex-1,False)
+  if LazPaintInstance.Image.CurrentLayerIndex > 0 then
+    tempUnder := LazPaintInstance.Image.ComputeFlatImage(0,LazPaintInstance.Image.CurrentLayerIndex-1,False)
   else
     tempUnder := TBGRABitmap.Create(1,1);
-  if ublendop.ShowBlendOpDialog(LazPaintInstance, blendOp, tempUnder,LazPaintInstance.Image.SelectedImageLayerReadOnly) then
+  if ublendop.ShowBlendOpDialog(LazPaintInstance, blendOp, tempUnder,LazPaintInstance.Image.CurrentLayerReadOnly) then
   begin
     updatingImageOnly := true;
-    LazPaintInstance.Image.BlendOperation[LazPaintInstance.Image.currentImageLayerIndex] := blendOp;
+    LazPaintInstance.Image.BlendOperation[LazPaintInstance.Image.CurrentLayerIndex] := blendOp;
     updatingImageOnly := false;
     UpdateComboBlendOp;
   end;
   tempUnder.Free;
   LazPaintInstance.ShowTopmost(topmostInfo);
-  if LazPaintInstance.Image.currentImageLayerIndex = 0 then
+  if LazPaintInstance.Image.CurrentLayerIndex = 0 then
     LazPaintInstance.ToolManager.ToolPopup(tpmBlendOpBackground);
 end;
 
@@ -715,8 +792,8 @@ begin
         else
           blendOp := StrToBlendOperation(itemStr);
         updatingImageOnly := true;
-        LazPaintInstance.Image.BlendOperation[LazPaintInstance.Image.currentImageLayerIndex] := blendOp;
-        if LazPaintInstance.Image.currentImageLayerIndex = 0 then
+        LazPaintInstance.Image.BlendOperation[LazPaintInstance.Image.CurrentLayerIndex] := blendOp;
+        if LazPaintInstance.Image.CurrentLayerIndex = 0 then
           LazPaintInstance.ToolManager.ToolPopup(tpmBlendOpBackground);
         updatingImageOnly := false;
       end else
@@ -770,7 +847,7 @@ begin
       begin
         if i < LazPaintInstance.Image.NbLayers then
         begin
-          if (i <> LazPaintInstance.image.currentImageLayerIndex) and not renaming then
+          if (i <> LazPaintInstance.image.CurrentLayerIndex) and not renaming then
             HandleSelectLayer(i,x,y)
           else
           begin

+ 32 - 2
lazpaint/uloadimage.pas

@@ -5,18 +5,20 @@ unit ULoadImage;
 interface
 
 uses
-  Classes, SysUtils, LazPaintType, BGRABitmap;
+  Classes, SysUtils, LazPaintType, BGRABitmap, BGRALayers, BGRASVGOriginal;
 
 function LoadFlatImageUTF8(AFilename: string; ASkipDialog: boolean = false): TImageEntry;
 procedure FreeMultiImage(var images: ArrayOfImageEntry);
 function AbleToLoadUTF8(AFilename: string): boolean;
+function LoadSVGImageUTF8(AFilename: string): TBGRALayeredBitmap;
+function LoadSVGOriginalUTF8(AFilename: string): TBGRALayerSVGOriginal;
 
 implementation
 
 uses FileUtil, BGRAAnimatedGif, Graphics, UMultiImage,
   BGRAReadLzp, LCLProc, BGRABitmapTypes, BGRAReadPng,
   UFileSystem, BGRAIconCursor, BGRAReadTiff,
-  Dialogs;
+  Dialogs, math;
 
 function LoadIcoMultiImageFromStream(AStream: TStream): ArrayOfImageEntry;
 var ico: TBGRAIconCursor; i: integer;
@@ -126,6 +128,34 @@ begin
   end;
 end;
 
+function LoadSVGImageUTF8(AFilename: string): TBGRALayeredBitmap;
+var
+  svg: TBGRALayerSVGOriginal;
+begin
+  svg := LoadSVGOriginalUTF8(AFilename);
+  result := TBGRALayeredBitmap.Create(ceil(svg.Width),ceil(svg.Height));
+  result.AddLayerFromOwnedOriginal(svg);
+  result.RenderLayerFromOriginal(0);
+end;
+
+function LoadSVGOriginalUTF8(AFilename: string): TBGRALayerSVGOriginal;
+var
+  svg: TBGRALayerSVGOriginal;
+  s: TStream;
+begin
+  s := FileManager.CreateFileStream(AFilename, fmOpenRead or fmShareDenyWrite);
+  result := nil;
+  try
+    svg := TBGRALayerSVGOriginal.Create;
+    svg.LoadFromStream(s);
+    result:= svg;
+    svg:= nil;
+  finally
+    s.Free;
+    svg.Free;
+  end;
+end;
+
 function LoadFlatImageUTF8(AFilename: string; ASkipDialog: boolean): TImageEntry;
 var
   formMultiImage: TFMultiImage;

+ 1 - 1
lazpaint/umenu.pas

@@ -211,7 +211,7 @@ begin
   AddMenus('MenuEdit',   'EditUndo,EditRedo,-,EditCut,EditCopy,EditPaste,EditPasteAsNew,EditPasteAsNewLayer,EditDeleteSelection,-,EditSelectAll,EditInvertSelection,EditSelectionFit,EditDeselect');
   AddMenus('MenuSelect', 'EditSelection,FileLoadSelection,FileSaveSelectionAs,-,EditSelectAll,EditInvertSelection,EditSelectionFit,EditDeselect,-,ToolSelectRect,ToolSelectEllipse,ToolSelectPoly,ToolSelectSpline,-,ToolMoveSelection,ToolRotateSelection,-,ToolSelectPen,ToolMagicWand');
   AddMenus('MenuView',   'ViewZoomOriginal,ViewZoomIn,ViewZoomOut,ViewZoomFit,-,*');
-  AddMenus('MenuImage',  'ImageCrop,ImageCropLayer,ImageFlatten,-,MenuRemoveTransparency,-,ImageChangeCanvasSize,ImageRepeat,-,ImageResample,ImageSmartZoom3,-,ImageRotateCW,ImageRotateCCW,ImageHorizontalFlip,MenuHorizFlipSub,ImageVerticalFlip,MenuVertFlipSub');
+  AddMenus('MenuImage',  'ImageCrop,ImageCropLayer,ImageFlatten,MenuRemoveTransparency,-,ImageNegative,ImageLinearNegative,ImageSwapRedBlue,-,ImageChangeCanvasSize,ImageRepeat,-,ImageResample,ImageSmartZoom3,-,ImageRotateCW,ImageRotateCCW,ImageHorizontalFlip,MenuHorizFlipSub,ImageVerticalFlip,MenuVertFlipSub');
   AddMenus('MenuRemoveTransparency', 'ImageClearAlpha,ImageFillBackground');
   AddMenus('MenuFilter', 'MenuRadialBlur,FilterBlurMotion,FilterBlurCustom,FilterPixelate,-,FilterSharpen,FilterSmooth,FilterNoise,FilterMedian,FilterClearType,FilterClearTypeInverse,FilterFunction,-,FilterContour,FilterEmboss,FilterPhong,-,FilterSphere,FilterTwirl,FilterCylinder');
   AddMenus('MenuRadialBlur',  'FilterBlurBox,FilterBlurFast,FilterBlurRadial,FilterBlurCorona,FilterBlurDisk');

+ 2 - 2
lazpaint/upalettetoolbar.pas

@@ -491,8 +491,8 @@ begin
   quant := TBGRAColorQuantizer.Create(FColors, not FTransparentPalette);
   LayerAction := nil;
   try
-    LayerAction := TLayerAction.Create(LazPaintInstance.Image);
-    LayerAction.ReplaceSelectedLayer(quant.GetDitheredBitmap(ADither,LayerAction.SelectedImageLayer) as TBGRABitmap, True);
+    LayerAction := LazPaintInstance.Image.CreateAction;
+    quant.ApplyDitheringInplace(ADither,LayerAction.SelectedImageLayer);
     LazPaintInstance.image.LayerMayChangeCompletely(LayerAction.SelectedImageLayer);
     LayerAction.Validate;
   except

+ 1 - 1
lazpaint/uposterize.pas

@@ -47,7 +47,7 @@ var FPosterize: TFPosterize;
 begin
   FPosterize := TFPosterize.Create(nil);
   try
-    FPosterize.FFilterConnector := TFilterConnector.Create(AInstance, AParameters);
+    FPosterize.FFilterConnector := TFilterConnector.Create(AInstance, AParameters, false);
     FPosterize.FFilterConnector.OnTryStopAction := @FPosterize.OnTryStopAction;
   except
     on ex: exception do

+ 1 - 0
lazpaint/uresourcestrings.pas

@@ -166,6 +166,7 @@ resourcestring
   rsOpenMultipleImageFiles='Open multiple image files';
   rsMoreThanOneFile='You are trying to open more than one file. How would you like these files to be opened?';
   rsOpenFilesAsLayers='Open files as layers in a single image';
+  rsTooManyLayers='Too many layers';
   rsAddToImageList='Add files to the image processing list';
   rsOpenFirstFileOnly='Open the first file only';
   rsLayeredImage = 'Layered image';

+ 35 - 35
lazpaint/uscripting.pas

@@ -87,41 +87,41 @@ type
     function AddStringList(const AName: string): TScriptVariableReference;
     function GetVariable(const AName: string): TScriptVariableReference;
     function IsDefined(const AName: string): boolean;
-    class procedure ClearList(const ADest: TScriptVariableReference);
-    class function AppendFloat(const ADest: TScriptVariableReference; AValue: double): boolean;
-    class function AssignFloat(const ADest: TScriptVariableReference; AValue: double): boolean;
-    class function AssignFloatAt(const ADest: TScriptVariableReference; AIndex: NativeInt; AValue: double): boolean;
-    class function AppendInteger(const ADest: TScriptVariableReference; AValue: TScriptInteger): boolean;
-    class function AssignInteger(const ADest: TScriptVariableReference; AValue: TScriptInteger): boolean;
-    class function AssignIntegerAt(const ADest: TScriptVariableReference; AIndex: NativeInt; AValue: TScriptInteger): boolean;
-    class function AppendObject(const ADest: TScriptVariableReference; AValue: TScriptInteger): boolean;
-    class function AssignObject(const ADest: TScriptVariableReference; AValue: TScriptInteger): boolean;
-    class function AssignObjectAt(const ADest: TScriptVariableReference; AIndex: NativeInt; AValue: TScriptInteger): boolean;
-    class function AppendBoolean(const ADest: TScriptVariableReference; AValue: boolean): boolean;
-    class function AssignBoolean(const ADest: TScriptVariableReference; AValue: boolean): boolean;
-    class function AppendString(const ADest: TScriptVariableReference; AValue: string): boolean;
-    class function AssignString(const ADest: TScriptVariableReference; AValue: string): boolean;
-    class function AppendPixel(const ADest: TScriptVariableReference; const AValue: TBGRAPixel): boolean;
-    class function AssignPixel(const ADest: TScriptVariableReference; const AValue: TBGRAPixel): boolean;
-    class function AssignList(const ADest: TScriptVariableReference; AListExpr: string): TInterpretationErrors;
-    class function AssignVariable(const ADest, ASource: TScriptVariableReference): boolean;
-    class function IsReferenceDefined(const AReference: TScriptVariableReference): boolean;
-    class function GetFloat(const ASource: TScriptVariableReference) : double;
-    class function GetInteger(const ASource: TScriptVariableReference) : TScriptInteger;
-    class function GetObject(const ASource: TScriptVariableReference) : TScriptInteger;
-    class function GetBoolean(const ASource: TScriptVariableReference) : boolean;
-    class function GetString(const ASource: TScriptVariableReference) : string;
-    class function GetPixel(const ASource: TScriptVariableReference) : TBGRAPixel;
-    class function GetSubset(const ASource: TScriptVariableReference) : TVariableSet;
-    class function GetList(const ASource: TScriptVariableReference) : string;
-    class function GetListCount(const ASource: TScriptVariableReference) : NativeInt;
-    class function GetFloatAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : double;
-    class function GetIntegerAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : TScriptInteger;
-    class function GetObjectAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : TScriptInteger;
-    class function GetBooleanAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : boolean;
-    class function GetStringAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : string;
-    class function GetPixelAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : TBGRAPixel;
-    class function RemoveAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : boolean;
+    class procedure ClearList(const ADest: TScriptVariableReference); static;
+    class function AppendFloat(const ADest: TScriptVariableReference; AValue: double): boolean; overload; static;
+    class function AssignFloat(const ADest: TScriptVariableReference; AValue: double): boolean; overload; static;
+    class function AssignFloatAt(const ADest: TScriptVariableReference; AIndex: NativeInt; AValue: double): boolean; static;
+    class function AppendInteger(const ADest: TScriptVariableReference; AValue: TScriptInteger): boolean; overload; static;
+    class function AssignInteger(const ADest: TScriptVariableReference; AValue: TScriptInteger): boolean; overload; static;
+    class function AssignIntegerAt(const ADest: TScriptVariableReference; AIndex: NativeInt; AValue: TScriptInteger): boolean; static;
+    class function AppendObject(const ADest: TScriptVariableReference; AValue: TScriptInteger): boolean; overload; static;
+    class function AssignObject(const ADest: TScriptVariableReference; AValue: TScriptInteger): boolean; overload; static;
+    class function AssignObjectAt(const ADest: TScriptVariableReference; AIndex: NativeInt; AValue: TScriptInteger): boolean; static;
+    class function AppendBoolean(const ADest: TScriptVariableReference; AValue: boolean): boolean; overload; static;
+    class function AssignBoolean(const ADest: TScriptVariableReference; AValue: boolean): boolean; overload; static;
+    class function AppendString(const ADest: TScriptVariableReference; AValue: string): boolean; overload; static;
+    class function AssignString(const ADest: TScriptVariableReference; AValue: string): boolean; overload; static;
+    class function AppendPixel(const ADest: TScriptVariableReference; const AValue: TBGRAPixel): boolean; overload; static;
+    class function AssignPixel(const ADest: TScriptVariableReference; const AValue: TBGRAPixel): boolean; overload; static;
+    class function AssignList(const ADest: TScriptVariableReference; AListExpr: string): TInterpretationErrors; static;
+    class function AssignVariable(const ADest, ASource: TScriptVariableReference): boolean; static;
+    class function IsReferenceDefined(const AReference: TScriptVariableReference): boolean; static;
+    class function GetFloat(const ASource: TScriptVariableReference) : double; static;
+    class function GetInteger(const ASource: TScriptVariableReference) : TScriptInteger; static;
+    class function GetObject(const ASource: TScriptVariableReference) : TScriptInteger; static;
+    class function GetBoolean(const ASource: TScriptVariableReference) : boolean; static;
+    class function GetString(const ASource: TScriptVariableReference) : string; static;
+    class function GetPixel(const ASource: TScriptVariableReference) : TBGRAPixel; static;
+    class function GetSubset(const ASource: TScriptVariableReference) : TVariableSet; static;
+    class function GetList(const ASource: TScriptVariableReference) : string; static;
+    class function GetListCount(const ASource: TScriptVariableReference) : NativeInt; static;
+    class function GetFloatAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : double; static;
+    class function GetIntegerAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : TScriptInteger; static;
+    class function GetObjectAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : TScriptInteger; static;
+    class function GetBooleanAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : boolean; static;
+    class function GetStringAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : string; static;
+    class function GetPixelAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : TBGRAPixel; static;
+    class function RemoveAt(const ASource: TScriptVariableReference; AIndex: NativeInt) : boolean; static;
     function Duplicate: TVariableSet;
     function CopyValuesTo(ASet: TVariableSet): boolean;
     property FunctionName: string read FFunctionName;

+ 2 - 2
lazpaint/uselectionhighlight.pas

@@ -68,8 +68,8 @@ var filter: TBGRAEmbossHightlightScanner;
   selectionBounds: TRect;
   gridCoverage, extendedGridCoverage: TRect;
 begin
-  selection := FImage.SelectionReadonly;
-  selectionBounds := FImage.SelectionBounds;
+  selection := FImage.SelectionMaskReadonly;
+  selectionBounds := FImage.SelectionMaskBounds;
   if (selection = nil) or (selection.Width = 0) or (selection.Width = 0) or
      IsRectEmpty(selectionBounds) then
   begin

+ 1 - 1
lazpaint/ushiftcolors.pas

@@ -196,7 +196,7 @@ var gsbaOptionFromConfig: boolean;
     topmostInfo: TTopMostInfo;
 begin
   try
-    FFilterConnector := TFilterConnector.Create(AInstance,AParameters);
+    FFilterConnector := TFilterConnector.Create(AInstance,AParameters,false);
     FFilterConnector.OnTryStopAction := @OnTryStopAction;
   except
     on ex: exception do

+ 474 - 300
lazpaint/ustatetype.pas

@@ -5,7 +5,7 @@ unit UStateType;
 interface
 
 uses
-  Types, Classes, SysUtils, BGRABitmap, BGRABitmapTypes, BGRALayers;
+  Types, Classes, SysUtils, BGRABitmap, BGRABitmapTypes, BGRALayers, fgl;
 
 const MinSizeToCompress = 512; //set to 1 if you want always compression
 const MinSerializedSize = 16384;
@@ -20,6 +20,7 @@ type
     procedure ApplyTo(AState: TState); virtual; abstract;
     procedure UnapplyTo(AState: TState); virtual; abstract;
     function UsedMemory: int64; virtual;
+    function ToString: ansistring; override;
   end;
 
   TState = class
@@ -55,6 +56,31 @@ type
     property IsIdentity: boolean read GetIsIdentity;
   end;
 
+  TImageDifferenceList = specialize TFPGObjectList<TCustomImageDifference>;
+
+  { TComposedImageDifference }
+
+  TComposedImageDifference = class(TCustomImageDifference)
+  private
+    function GetCount: integer;
+  protected
+    FDiffs: TImageDifferenceList;
+    function GetIsIdentity: boolean; override;
+    function GetImageDifferenceKind: TImageDifferenceKind; override;
+    function GetChangingBounds: TRect; override;
+    function GetChangingBoundsDefined: boolean; override;
+  public
+    constructor Create;
+    function TryCompress: boolean; override;
+    function UsedMemory: int64; override;
+    procedure Add(ADiff: TCustomImageDifference);
+    procedure AddRange(AComposed: TComposedImageDifference);
+    procedure ApplyTo(AState: TState); override;
+    procedure UnapplyTo(AState: TState); override;
+    function ToString: ansistring; override;
+    property Count: integer read GetCount;
+  end;
+
 {*********** Layer info *************}
 
 type
@@ -64,83 +90,89 @@ type
     BlendOp: TBlendOperation;
     Name: string;
     Opactiy: byte;
+    Offset: TPoint;
   end;
 
 procedure ApplyLayerInfo(AInfo: TLayerInfo; ALayeredBitmap: TBGRALayeredBitmap; AIndex: integer);
-procedure ApplyLayerInfo(AInfo: TLayerInfo; ALayeredBitmap: TBGRALayeredBitmap);
 function GetLayerInfo(ALayeredBitmap: TBGRALayeredBitmap; AIndex: integer): TLayerInfo;
 
 {*********** Inversible **************}
 
 type
-  TInversibleAction = (iaHorizontalFlip, iaVerticalFlip, iaRotateCW, iaRotateCCW, iaRotate180, iaSwapRedBlue, iaLinearNegative);
+  TInversibleAction = (iaHorizontalFlip, iaHorizontalFlipLayer, iaVerticalFlip, iaVerticalFlipLayer, iaRotateCW, iaRotateCCW, iaRotate180, iaSwapRedBlue, iaLinearNegative);
+
+const
+  InversibleActionStr : array[TInversibleAction] of string =
+    ('HorizontalFlip', 'HorizontalFlipLayer', 'VerticalFlip', 'VerticalFlipLayer', 'RotateCW', 'RotateCCW', 'Rotate180', 'SwapRedBlue', 'LinearNegative');
 
 function GetInverseAction(AAction: TInversibleAction): TInversibleAction;
 function CanCombineInversibleAction(AAction1, AAction2: TInversibleAction; out
   ACombined: TInversibleAction): boolean;
 
 type
+  TCustomImageDiff = class
+  private
+    FSavedFilename: string;
+    FCompressedData: TMemoryStream;
+    procedure DiscardFile;
+    function GetIsIdentity: boolean; virtual;
+    procedure Init(Image1,Image2: TBGRABitmap; {%H-}AChangeRect: TRect); virtual;
+    function SerializeCompressedData: boolean;
+    procedure UnserializeCompressedData;
+  public
+    SizeBefore, SizeAfter: TSize;
+    constructor Create(Image1,Image2: TBGRABitmap; AChangeRect: TRect); overload;
+    constructor Create(Image1,Image2: TBGRABitmap); overload;
+    procedure ApplyInPlace(ADest: TBGRABitmap; {%H-}AReverse: boolean); virtual; abstract;
+    function ApplyInNew(ASource: TBGRABitmap; AReverse: boolean): TBGRABitmap;
+    function ApplyCanCreateNew(ASource: TBGRABitmap; AReverse: boolean): TBGRABitmap;
+    function Compress: boolean; virtual;
+    destructor Destroy; override;
+    function UsedMemory: int64;
+    property IsIdentity: boolean read GetIsIdentity;
+  end;
+
   { TImageDiff }
 
-  TImageDiff = class
+  TImageDiff = class(TCustomImageDiff)
   private
     FChangeRect: TRect;
     FUncompressedData: record
       data0,data1,data2,data3: PByte;
       dataLen: PtrUInt;
     end;
-    FSavedFilename: string;
-    FCompressedData: TMemoryStream;
-    procedure DiscardFile;
-    function GetIsIdentity: boolean;
+    function GetIsIdentity: boolean; override;
     procedure Decompress;
-    procedure Init(Image1,Image2: TBGRABitmap; AChangeRect: TRect);
+    procedure Init(Image1,Image2: TBGRABitmap; AChangeRect: TRect); override;
   public
-    SizeBefore, SizeAfter: TSize;
-    constructor Create(Image1,Image2: TBGRABitmap; AChangeRect: TRect); overload;
-    constructor Create(Image1,Image2: TBGRABitmap); overload;
-    procedure Apply(ADest: TBGRABitmap; {%H-}AReverse: boolean);
-    function Compress: boolean;
+    procedure ApplyInPlace(ADest: TBGRABitmap; {%H-}AReverse: boolean); override;
+    function Compress: boolean; override;
     destructor Destroy; override;
     function UsedMemory: int64;
-    property IsIdentity: boolean read GetIsIdentity;
     property ChangeRect: TRect read FChangeRect;
   end;
 
-function ComputeFromImageDiff(FromImage: TBGRABitmap; ADiff: TImageDiff; AReverse: boolean): TBGRABitmap;
-procedure ApplyImageDiffAndReplace(var AImage: TBGRABitmap; ADiff: TImageDiff; AReverse: boolean);
-
 type
   { TGrayscaleImageDiff }
 
-  TGrayscaleImageDiff = class
+  TGrayscaleImageDiff = class(TCustomImageDiff)
   private
     FChangeRect: TRect;
     FUncompressedData: record
       data0: PByte;
       dataLen: PtrUInt;
     end;
-    FSavedFilename: string;
-    FCompressedData: TMemoryStream;
-    procedure DiscardFile;
-    function GetIsIdentity: boolean;
+    function GetIsIdentity: boolean; override;
     procedure Decompress;
-    procedure Init(Image1,Image2: TBGRABitmap; AChangeRect: TRect);
+    procedure Init(Image1,Image2: TBGRABitmap; AChangeRect: TRect); override;
   public
-    SizeBefore, SizeAfter: TSize;
-    constructor Create(Image1,Image2: TBGRABitmap; AChangeRect: TRect); overload;
-    constructor Create(Image1,Image2: TBGRABitmap); overload;
-    procedure Apply(ADest: TBGRABitmap; {%H-}AReverse: boolean);
-    function Compress: boolean;
+    procedure ApplyInPlace(ADest: TBGRABitmap; {%H-}AReverse: boolean); override;
+    function Compress: boolean; override;
     destructor Destroy; override;
     function UsedMemory: int64;
-    property IsIdentity: boolean read GetIsIdentity;
     property ChangeRect: TRect read FChangeRect;
   end;
 
-function ComputeFromGrayscaleImageDiff(FromImage: TBGRABitmap; ADiff: TGrayscaleImageDiff; AReverse: boolean): TBGRABitmap;
-procedure ApplyGrayscaleImageDiffAndReplace(var AImage: TBGRABitmap; ADiff: TGrayscaleImageDiff; AReverse: boolean);
-
 type
   { TStoredImage }
 
@@ -150,13 +182,34 @@ type
     function GetBitmap: TBGRABitmap;
   end;
 
+  { TStoredLayer }
+
+  TStoredLayer = class(TStoredImage)
+  private
+    function GetId: integer;
+  protected
+    FInfo: TLayerInfo;
+    FIndex: integer;
+    FOriginalData: TMemoryStream;
+    FOriginalKnown: boolean;
+    FOriginalRenderStatus: TOriginalRenderStatus;
+    FOriginalMatrix: TAffineMatrix;
+    FOriginalDraft: boolean;
+  public
+    constructor Create(ALayeredImage: TBGRALayeredBitmap; AIndex: integer);
+    procedure Restore(ALayeredImage: TBGRALayeredBitmap);
+    procedure Replace(ALayeredImage: TBGRALayeredBitmap);
+    property LayerIndex: integer read FIndex;
+    property LayerId: integer read GetId;
+  end;
+
 implementation
 
 uses Math, BGRALzpCommon, UFileSystem;
 
-{ TGrayscaleImageDiff }
+{ TCustomImageDiff }
 
-procedure TGrayscaleImageDiff.DiscardFile;
+procedure TCustomImageDiff.DiscardFile;
 begin
   if FSavedFilename <> '' then
   begin
@@ -169,12 +222,59 @@ begin
   end;
 end;
 
-function TGrayscaleImageDiff.GetIsIdentity: boolean;
+function TCustomImageDiff.GetIsIdentity: boolean;
 begin
-  result := (SizeBefore.cx = SizeAfter.cx) and (SizeBefore.cy = SizeAfter.cy) and (FUncompressedData.dataLen=0);
+  result := (SizeBefore.cx = SizeAfter.cx) and (SizeBefore.cy = SizeAfter.cy);
 end;
 
-procedure TGrayscaleImageDiff.Decompress;
+procedure TCustomImageDiff.Init(Image1, Image2: TBGRABitmap; AChangeRect: TRect);
+begin
+  if Image1 = nil then
+  begin
+    SizeBefore.cx := 0;
+    SizeBefore.cy := 0;
+  end else
+  begin
+    SizeBefore.cx := Image1.Width;
+    SizeBefore.cy := Image1.Height;
+  end;
+  if Image2 = nil then
+  begin
+    SizeAfter.cx := 0;
+    SizeAfter.cy := 0;
+  end else
+  begin
+    SizeAfter.cx := Image2.Width;
+    SizeAfter.cy := Image2.Height;
+  end;
+end;
+
+function TCustomImageDiff.SerializeCompressedData: boolean;
+var
+  savedFile: TStream;
+begin
+  FSavedFilename := GetTempFileName;
+  try
+    savedFile := FileManager.CreateFileStream(FSavedFilename,fmCreate);
+    try
+      FCompressedData.Position := 0;
+      savedFile.CopyFrom(FCompressedData, FCompressedData.Size);
+      FreeAndNil(FCompressedData);
+      result := true;
+    finally
+      savedFile.Free;
+    end;
+  except
+    on ex: exception do
+    begin
+      if FileManager.FileExists(FSavedFilename) then FileManager.DeleteFile(FSavedFilename);
+      FSavedFilename := '';
+      result := false;
+    end;
+  end;
+end;
+
+procedure TCustomImageDiff.UnserializeCompressedData;
 var stream: TStream;
 begin
   if (FCompressedData = nil) and (FSavedFilename <> '') then
@@ -188,6 +288,228 @@ begin
     end;
     stream.free;
   end;
+end;
+
+constructor TCustomImageDiff.Create(Image1, Image2: TBGRABitmap;
+  AChangeRect: TRect);
+begin
+  Init(Image1,Image2,AChangeRect);
+end;
+
+constructor TCustomImageDiff.Create(Image1, Image2: TBGRABitmap);
+var
+  r: TRect;
+begin
+  r := rect(0,0,0,0);
+  if Image1 <> nil then
+  begin
+    if image1.Width > r.Right then r.Right:= Image1.Width;
+    if image1.Height > r.Bottom then r.Bottom:= Image1.Height;
+  end;
+  if Image2 <> nil then
+  begin
+    if image2.Width > r.Right then r.Right:= Image2.Width;
+    if image2.Height > r.Bottom then r.Bottom:= Image2.Height;
+  end;
+  Init(Image1,Image2,r);
+end;
+
+function TCustomImageDiff.ApplyInNew(ASource: TBGRABitmap; AReverse: boolean): TBGRABitmap;
+var
+  DestSize: TSize;
+begin
+  if (self = nil) or IsIdentity then
+    result := ASource.Duplicate as TBGRABitmap
+  else
+  begin
+    if AReverse then DestSize := SizeBefore else
+      DestSize := SizeAfter;
+
+    if (DestSize.cx = 0) or (DestSize.cy = 0) then
+      result := nil
+    else
+    begin
+      result := TBGRABitmap.Create(Destsize.cx,Destsize.cy);
+      if ASource <> nil then
+        result.PutImage(0,0,ASource,dmSet);
+      ApplyInPlace(result, AReverse);
+    end;
+  end;
+end;
+
+function TCustomImageDiff.ApplyCanCreateNew(ASource: TBGRABitmap;
+  AReverse: boolean): TBGRABitmap;
+begin
+  if (self = nil) or IsIdentity then exit(ASource); //keep
+
+  if (SizeAfter.cx <> SizeBefore.cx) or
+     (SizeAfter.cy <> SizeBefore.cy) then
+     exit(ApplyInNew(ASource, AReverse))
+  else
+  begin
+    ApplyInPlace(ASource, AReverse);
+    exit(ASource);
+  end;
+end;
+
+function TCustomImageDiff.Compress: boolean;
+begin
+  result := false;
+end;
+
+destructor TCustomImageDiff.Destroy;
+begin
+  FreeAndnil(FCompressedData);
+  DiscardFile;
+  inherited Destroy;
+end;
+
+function TCustomImageDiff.UsedMemory: int64;
+begin
+  if Assigned(FCompressedData) then
+    result := FCompressedData.Size
+  else
+    result := 0;
+end;
+
+{ TComposedImageDifference }
+
+function TComposedImageDifference.GetCount: integer;
+begin
+  result := FDiffs.Count;
+end;
+
+function TComposedImageDifference.GetIsIdentity: boolean;
+var
+  i: Integer;
+begin
+  for i := 0 to FDiffs.Count-1 do
+    if not FDiffs[i].GetIsIdentity then exit(false);
+  exit(true);
+end;
+
+function TComposedImageDifference.GetImageDifferenceKind: TImageDifferenceKind;
+var
+  i: Integer;
+begin
+  result := idkChangeStack;
+  for i := 0 to FDiffs.Count-1 do
+    case FDiffs[i].GetImageDifferenceKind of
+      idkChangeImageAndSelection: result := idkChangeImageAndSelection;
+      idkChangeSelection: if result in[idkChangeImage,idkChangeLayer,idkChangeImageAndSelection] then
+                            result := idkChangeImageAndSelection
+                          else result := idkChangeSelection;
+      idkChangeImage: if result in[idkChangeImageAndSelection,idkChangeSelection] then
+                            result := idkChangeImageAndSelection
+                          else result := idkChangeImage;
+      idkChangeLayer: if result in[idkChangeImageAndSelection,idkChangeSelection] then
+                            result := idkChangeImageAndSelection
+                      else if result = idkChangeStack then
+                        result := idkChangeLayer;
+    end;
+end;
+
+function TComposedImageDifference.GetChangingBounds: TRect;
+var
+  i: Integer;
+  r: TRect;
+begin
+  result:= EmptyRect;
+  for i := 0 to FDiffs.Count-1 do
+  begin
+    r := FDiffs[i].GetChangingBounds;
+    if not IsRectEmpty(r) then
+    begin
+      if IsRectEmpty(result) then result:= r
+      else UnionRect(result, result,r);
+    end;
+  end;
+end;
+
+function TComposedImageDifference.GetChangingBoundsDefined: boolean;
+var
+  i: Integer;
+begin
+  for i := 0 to FDiffs.Count-1 do
+    if not FDiffs[i].GetChangingBoundsDefined then exit(false);
+  exit(true);
+end;
+
+constructor TComposedImageDifference.Create;
+begin
+  FDiffs := TImageDifferenceList.Create;
+end;
+
+function TComposedImageDifference.TryCompress: boolean;
+var
+  i: Integer;
+begin
+  for i := 0 to FDiffs.Count-1 do
+    if FDiffs[i].TryCompress then exit(true);
+  exit(false);
+end;
+
+function TComposedImageDifference.UsedMemory: int64;
+var
+  i: Integer;
+begin
+  result := 0;
+  for i := 0 to FDiffs.Count-1 do
+    inc(result, FDiffs[i].UsedMemory);
+end;
+
+procedure TComposedImageDifference.Add(ADiff: TCustomImageDifference);
+begin
+  FDiffs.Add(ADiff);
+end;
+
+procedure TComposedImageDifference.AddRange(AComposed: TComposedImageDifference);
+var
+  i: Integer;
+begin
+  for i:= 0 to AComposed.Count-1 do
+    Add(AComposed.FDiffs[i]);
+end;
+
+procedure TComposedImageDifference.ApplyTo(AState: TState);
+var
+  i: Integer;
+begin
+  for i := 0 to FDiffs.Count-1 do
+    FDiffs[i].ApplyTo(AState);
+end;
+
+procedure TComposedImageDifference.UnapplyTo(AState: TState);
+var
+  i: Integer;
+begin
+  for i := FDiffs.Count-1 downto 0 do
+    FDiffs[i].UnapplyTo(AState);
+end;
+
+function TComposedImageDifference.ToString: ansistring;
+var
+  i: Integer;
+begin
+  Result:= '[';
+  for i := 0 to Count-1 do
+  begin
+    if i <> 0 then result += ', ';
+    result += FDiffs[i].ToString;
+  end;
+  result += ']';
+end;
+
+{ TGrayscaleImageDiff }
+
+function TGrayscaleImageDiff.GetIsIdentity: boolean;
+begin
+  result := inherited GetIsIdentity and (FUncompressedData.dataLen=0);
+end;
+
+procedure TGrayscaleImageDiff.Decompress;
+begin
+  UnserializeCompressedData;
   if FCompressedData <> nil then
   begin
     FCompressedData.Position := 0;
@@ -207,28 +529,9 @@ var tx,ty: integer;
   p: PBGRAPixel;
   uncompressedChangeRect: TRect;
 begin
-  FUncompressedData.dataLen := 0;
+  inherited Init(Image1,Image2,AChangeRect);
   FChangeRect := EmptyRect;
-  if Image1 = nil then
-  begin
-    SizeBefore.cx := 0;
-    SizeBefore.cy := 0;
-  end else
-  begin
-    SizeBefore.cx := Image1.Width;
-    SizeBefore.cy := Image1.Height;
-  end;
-
-  if Image2 = nil then
-  begin
-    SizeAfter.cx := 0;
-    SizeAfter.cy := 0;
-  end else
-  begin
-    SizeAfter.cx := Image2.Width;
-    SizeAfter.cy := Image2.Height;
-  end;
-
+  FUncompressedData.dataLen := 0;
   tx := max(SizeBefore.cx,SizeAfter.cx);
   ty := max(SizeBefore.cy,SizeAfter.cy);
   if IntersectRect(AChangeRect, AChangeRect, rect(0,0,tx,ty)) then
@@ -269,31 +572,7 @@ begin
   end;
 end;
 
-constructor TGrayscaleImageDiff.Create(Image1, Image2: TBGRABitmap;
-  AChangeRect: TRect);
-begin
-  Init(Image1,Image2,AChangeRect);
-end;
-
-constructor TGrayscaleImageDiff.Create(Image1, Image2: TBGRABitmap);
-var
-  r: TRect;
-begin
-  r := rect(0,0,0,0);
-  if Image1 <> nil then
-  begin
-    if image1.Width > r.Right then r.Right:= Image1.Width;
-    if image1.Height > r.Bottom then r.Bottom:= Image1.Height;
-  end;
-  if Image2 <> nil then
-  begin
-    if image2.Width > r.Right then r.Right:= Image2.Width;
-    if image2.Height > r.Bottom then r.Bottom:= Image2.Height;
-  end;
-  Init(Image1,Image2,r);
-end;
-
-procedure TGrayscaleImageDiff.Apply(ADest: TBGRABitmap; AReverse: boolean);
+procedure TGrayscaleImageDiff.ApplyInPlace(ADest: TBGRABitmap; AReverse: boolean);
 var
   pdest: PBGRAPixel;
   data0: PByte;
@@ -325,8 +604,6 @@ begin
 end;
 
 function TGrayscaleImageDiff.Compress: boolean;
-var
-  FSavedFile: TStream;
 begin
   if (FUncompressedData.data0 <> nil) and
     ((FCompressedData <> nil) or (FSavedFilename <> '')) then
@@ -350,43 +627,19 @@ begin
     result := true;
 
     if FCompressedData.Size >= MinSerializedSize then
-    begin
-      FSavedFilename := GetTempFileName;
-      try
-        FSavedFile := FileManager.CreateFileStream(FSavedFilename,fmCreate);
-        try
-          FCompressedData.Position := 0;
-          FSavedFile.CopyFrom(FCompressedData, FCompressedData.Size);
-          FreeAndNil(FCompressedData);
-        finally
-          FSavedFile.Free;
-        end;
-      except
-        on ex: exception do
-        begin
-          if FileManager.FileExists(FSavedFilename) then FileManager.DeleteFile(FSavedFilename);
-          FSavedFilename := '';
-          result := false;
-        end;
-      end;
-    end;
+      SerializeCompressedData;
   end;
 end;
 
 destructor TGrayscaleImageDiff.Destroy;
 begin
-  FreeAndnil(FCompressedData);
   ReAllocMem(FUncompressedData.data0,0);
-  DiscardFile;
   inherited Destroy;
 end;
 
 function TGrayscaleImageDiff.UsedMemory: int64;
 begin
-  if Assigned(FCompressedData) then
-    result := FCompressedData.Size
-  else
-    result := 0;
+  result := inherited UsedMemory;
   if Assigned(FUncompressedData.data0) then inc(result,FUncompressedData.dataLen);
 end;
 
@@ -402,6 +655,11 @@ begin
   result := 0;
 end;
 
+function TStateDifference.ToString: ansistring;
+begin
+  Result:= ClassName;
+end;
+
 { TCustomImageDifference }
 
 function TCustomImageDifference.GetIsIdentity: boolean;
@@ -453,60 +711,6 @@ begin
   AState.saved:= FSavedBefore;
 end;
 
-{***********************************}
-
-procedure ApplyImageDiffAndReplace(var AImage: TBGRABitmap; ADiff: TImageDiff; AReverse: boolean);
-var tempBmp: TBGRABitmap;
-begin
-  if (ADiff = nil) or ADiff.IsIdentity then exit;
-  if (ADiff.SizeAfter.cx <> ADiff.SizeBefore.cx) or
-     (ADiff.SizeAfter.cy <> ADiff.SizeBefore.cy) then
-  begin
-    tempBmp := ComputeFromImageDiff(AImage, ADiff, AReverse);
-    FreeAndNil(AImage);
-    AImage := tempBmp;
-  end else
-    ADiff.Apply(AImage, AReverse);
-end;
-
-function ComputeFromGrayscaleImageDiff(FromImage: TBGRABitmap;
-  ADiff: TGrayscaleImageDiff; AReverse: boolean): TBGRABitmap;
-var
-  DestSize: TSize;
-begin
-  if (ADiff = nil) or ADiff.IsIdentity then
-  begin
-    result := FromImage.Duplicate as TBGRABitmap;
-    exit;
-  end;
-  if AReverse then DestSize := ADiff.SizeBefore else
-    DestSize := ADiff.SizeAfter;
-  if (DestSize.cx = 0) or (DestSize.cy = 0) then
-    result := nil
-  else
-  begin
-    result := TBGRABitmap.Create(Destsize.cx,Destsize.cy,BGRABlack);
-    if FromImage <> nil then
-      result.PutImage(0,0,FromImage,dmSet);
-    ADiff.Apply(result, AReverse);
-  end;
-end;
-
-procedure ApplyGrayscaleImageDiffAndReplace(var AImage: TBGRABitmap;
-  ADiff: TGrayscaleImageDiff; AReverse: boolean);
-var tempBmp: TBGRABitmap;
-begin
-  if (ADiff = nil) or ADiff.IsIdentity then exit;
-  if (ADiff.SizeAfter.cx <> ADiff.SizeBefore.cx) or
-     (ADiff.SizeAfter.cy <> ADiff.SizeBefore.cy) then
-  begin
-    tempBmp := ComputeFromGrayscaleImageDiff(AImage, ADiff, AReverse);
-    FreeAndNil(AImage);
-    AImage := tempBmp;
-  end else
-    ADiff.Apply(AImage, AReverse);
-end;
-
 {*********** Layer info *************}
 
 procedure ApplyLayerInfo(AInfo: TLayerInfo; ALayeredBitmap: TBGRALayeredBitmap; AIndex: integer);
@@ -518,15 +722,8 @@ begin
   ALayeredBitmap.BlendOperation[AIndex] := AInfo.BlendOp;
   ALayeredBitmap.LayerName[AIndex] := AInfo.Name;
   ALayeredBitmap.LayerOpacity[AIndex] := AInfo.Opactiy;
-end;
-
-procedure ApplyLayerInfo(AInfo: TLayerInfo; ALayeredBitmap: TBGRALayeredBitmap);
-var idx: integer;
-begin
-  idx := ALayeredBitmap.GetLayerIndexFromId(AInfo.Id);
-  if idx = -1 then
-    raise exception.Create('Layer not found');
-  ApplyLayerInfo(AInfo, ALayeredBitmap, idx);
+  if ALayeredBitmap.LayerOriginalGuid[AIndex] = GUID_NULL then
+    ALayeredBitmap.LayerOffset[AIndex] := AInfo.Offset;
 end;
 
 function GetLayerInfo(ALayeredBitmap: TBGRALayeredBitmap; AIndex: integer): TLayerInfo;
@@ -538,6 +735,7 @@ begin
   result.BlendOp := ALayeredBitmap.BlendOperation[AIndex];
   result.Name := ALayeredBitmap.LayerName[AIndex];
   result.Opactiy := ALayeredBitmap.LayerOpacity[AIndex];
+  result.Offset := ALayeredBitmap.LayerOffset[AIndex];
 end;
 
 {*********** Inversible **************}
@@ -589,64 +787,16 @@ begin
   end;
 end;
 
-{**************** Image diff ****************}
-
-function ComputeFromImageDiff(FromImage: TBGRABitmap; ADiff: TImageDiff; AReverse: boolean): TBGRABitmap;
-var
-  DestSize: TSize;
-begin
-  if (ADiff = nil) or ADiff.IsIdentity then
-  begin
-    result := FromImage.Duplicate as TBGRABitmap;
-    exit;
-  end;
-  if AReverse then DestSize := ADiff.SizeBefore else
-    DestSize := ADiff.SizeAfter;
-  if (DestSize.cx = 0) or (DestSize.cy = 0) then
-    result := nil
-  else
-  begin
-    result := TBGRABitmap.Create(Destsize.cx,Destsize.cy);
-    if FromImage <> nil then
-      result.PutImage(0,0,FromImage,dmSet);
-    ADiff.Apply(result, AReverse);
-  end;
-end;
-
 { TImageDiff }
 
-procedure TImageDiff.DiscardFile;
-begin
-  if FSavedFilename <> '' then
-  begin
-    try
-      if FileManager.FileExists(FSavedFilename) then
-        FileManager.DeleteFile(FSavedFilename);
-    except on ex:exception do begin end;
-    end;
-    FSavedFilename:= '';
-  end;
-end;
-
 function TImageDiff.GetIsIdentity: boolean;
 begin
-  result := (SizeBefore.cx = SizeAfter.cx) and (SizeBefore.cy = SizeAfter.cy) and (FUncompressedData.dataLen=0);
+  result := inherited GetIsIdentity and (FUncompressedData.dataLen=0);
 end;
 
 procedure TImageDiff.Decompress;
-var stream: TStream;
 begin
-  if (FCompressedData = nil) and (FSavedFilename <> '') then
-  begin
-    FCompressedData := TMemoryStream.Create;
-    stream := nil;
-    try
-      stream := FileManager.CreateFileStream(FSavedFilename,fmOpenRead or fmShareDenyWrite);
-      FCompressedData.CopyFrom(stream, stream.Size);
-    except
-    end;
-    stream.free;
-  end;
+  UnserializeCompressedData;
   if FCompressedData <> nil then
   begin
     FCompressedData.Position := 0;
@@ -672,28 +822,9 @@ var tx,ty: integer;
   v: DWord;
   uncompressedChangeRect: TRect;
 begin
-  FUncompressedData.dataLen := 0;
+  inherited Init(Image1, Image2, ChangeRect);
   FChangeRect := EmptyRect;
-  if Image1 = nil then
-  begin
-    SizeBefore.cx := 0;
-    SizeBefore.cy := 0;
-  end else
-  begin
-    SizeBefore.cx := Image1.Width;
-    SizeBefore.cy := Image1.Height;
-  end;
-
-  if Image2 = nil then
-  begin
-    SizeAfter.cx := 0;
-    SizeAfter.cy := 0;
-  end else
-  begin
-    SizeAfter.cx := Image2.Width;
-    SizeAfter.cy := Image2.Height;
-  end;
-
+  FUncompressedData.dataLen := 0;
   tx := max(SizeBefore.cx,SizeAfter.cx);
   ty := max(SizeBefore.cy,SizeAfter.cy);
   if IntersectRect(AChangeRect, AChangeRect, rect(0,0,tx,ty)) then
@@ -744,36 +875,14 @@ begin
   end;
 end;
 
-constructor TImageDiff.Create(Image1, Image2: TBGRABitmap; AChangeRect: TRect);
-begin
-  Init(Image1,Image2,AChangeRect);
-end;
-
-constructor TImageDiff.Create(Image1, Image2: TBGRABitmap);
-var
-  r: TRect;
-begin
-  r := rect(0,0,0,0);
-  if Image1 <> nil then
-  begin
-    if image1.Width > r.Right then r.Right:= Image1.Width;
-    if image1.Height > r.Bottom then r.Bottom:= Image1.Height;
-  end;
-  if Image2 <> nil then
-  begin
-    if image2.Width > r.Right then r.Right:= Image2.Width;
-    if image2.Height > r.Bottom then r.Bottom:= Image2.Height;
-  end;
-  Init(Image1,Image2,r);
-end;
-
-procedure TImageDiff.Apply(ADest: TBGRABitmap; AReverse: boolean);
+procedure TImageDiff.ApplyInPlace(ADest: TBGRABitmap; AReverse: boolean);
 var
   pdest: PDWord;
   data0,data1,data2,data3: PByte;
   r: TRect;
   xb,yb,w,offset: PtrUInt;
 begin
+  if ADest = nil then raise exception.Create('Unexpected nil reference');
   r := FChangeRect;
   w := FChangeRect.Right-FChangeRect.Left;
   if not IntersectRect(r, r,rect(0,0,ADest.Width,ADest.Height)) then exit;
@@ -798,8 +907,6 @@ begin
 end;
 
 function TImageDiff.Compress: boolean;
-var
-  FSavedFile: TStream;
 begin
   if ((FUncompressedData.data0 <> nil) or (FUncompressedData.data1 <> nil) or
     (FUncompressedData.data2 <> nil) or (FUncompressedData.data3 <> nil)) and
@@ -833,46 +940,22 @@ begin
     result := true;
 
     if FCompressedData.Size >= MinSerializedSize then
-    begin
-      FSavedFilename := GetTempFileName;
-      try
-        FSavedFile := FileManager.CreateFileStream(FSavedFilename,fmCreate);
-        try
-          FCompressedData.Position := 0;
-          FSavedFile.CopyFrom(FCompressedData, FCompressedData.Size);
-          FreeAndNil(FCompressedData);
-        finally
-          FSavedFile.Free;
-        end;
-      except
-        on ex: exception do
-        begin
-          if FileManager.FileExists(FSavedFilename) then FileManager.DeleteFile(FSavedFilename);
-          FSavedFilename := '';
-          result := false;
-        end;
-      end;
-    end;
+      SerializeCompressedData;
   end;
 end;
 
 destructor TImageDiff.Destroy;
 begin
-  FreeAndnil(FCompressedData);
   ReAllocMem(FUncompressedData.data0,0);
   ReAllocMem(FUncompressedData.data1,0);
   ReAllocMem(FUncompressedData.data2,0);
   ReAllocMem(FUncompressedData.data3,0);
-  DiscardFile;
   inherited Destroy;
 end;
 
 function TImageDiff.UsedMemory: int64;
 begin
-  if Assigned(FCompressedData) then
-    result := FCompressedData.Size
-  else
-    result := 0;
+  result := inherited UsedMemory;
   if Assigned(FUncompressedData.data0) then inc(result,FUncompressedData.dataLen);
   if Assigned(FUncompressedData.data1) then inc(result,FUncompressedData.dataLen);
   if Assigned(FUncompressedData.data2) then inc(result,FUncompressedData.dataLen);
@@ -883,13 +966,104 @@ end;
 
 constructor TStoredImage.Create(ABitmap: TBGRABitmap);
 begin
-  inherited Create(nil,ABitmap,rect(0,0,ABitmap.Width,ABitmap.Height));
+  if Assigned(ABitmap) then
+    inherited Create(nil,ABitmap,rect(0,0,ABitmap.Width,ABitmap.Height))
+  else
+    inherited Create(nil,ABitmap,EmptyRect)
 end;
 
 function TStoredImage.GetBitmap: TBGRABitmap;
 begin
   result := TBGRABitmap.Create(SizeAfter.cx, SizeAfter.cy);
-  Apply(result,false);
+  ApplyInPlace(result,false);
+end;
+
+{ TStoredLayer }
+
+function TStoredLayer.GetId: integer;
+begin
+  result := FInfo.Id;
+end;
+
+constructor TStoredLayer.Create(ALayeredImage: TBGRALayeredBitmap;
+  AIndex: integer);
+begin
+  FIndex := AIndex;
+  FInfo := GetLayerInfo(ALayeredImage, AIndex);
+  if ALayeredImage.LayerOriginalGuid[AIndex]<>GUID_NULL then
+  begin
+    FOriginalKnown := ALayeredImage.LayerOriginalKnown[AIndex];
+    FOriginalRenderStatus:= ALayeredImage.LayerOriginalRenderStatus[AIndex];
+
+    if FOriginalKnown then
+      inherited Create(nil)
+    else
+      inherited Create(ALayeredImage.LayerBitmap[AIndex]);
+
+    FOriginalData := TMemoryStream.Create;
+    ALayeredImage.SaveOriginalToStream(ALayeredImage.LayerOriginalGuid[AIndex], FOriginalData);
+    FOriginalMatrix := ALayeredImage.LayerOriginalMatrix[AIndex];
+    FOriginalDraft := ALayeredImage.LayerOriginalRenderStatus[AIndex] in[orsDraft,orsPartialDraft];
+  end else
+  begin
+    inherited Create(ALayeredImage.LayerBitmap[AIndex]);
+    FOriginalData := nil;
+  end;
+end;
+
+procedure TStoredLayer.Restore(ALayeredImage: TBGRALayeredBitmap);
+var
+  tempIdx, idxOrig: Integer;
+begin
+  if Assigned(FOriginalData) then
+  begin
+    FOriginalData.Position:= 0;
+    idxOrig := ALayeredImage.AddOriginalFromStream(FOriginalData, true);
+
+    if FOriginalKnown then
+    begin
+      tempIdx := ALayeredImage.AddLayerFromOriginal(ALayeredImage.Original[idxOrig].Guid, FOriginalMatrix);
+      ALayeredImage.RenderLayerFromOriginal(tempIdx, FOriginalDraft);
+    end else
+    begin
+      tempIdx := ALayeredImage.AddOwnedLayer(GetBitmap);
+      ALayeredImage.LayerOriginalGuid[tempIdx] := ALayeredImage.OriginalGuid[idxOrig];
+      ALayeredImage.LayerOriginalMatrix[tempIdx] := FOriginalMatrix;
+      ALayeredImage.LayerOriginalRenderStatus[tempIdx] := FOriginalRenderStatus;
+    end;
+  end else
+    tempIdx := ALayeredImage.AddOwnedLayer(GetBitmap);
+
+  ApplyLayerInfo(FInfo,ALayeredImage,tempIdx);
+  ALayeredImage.InsertLayer(FIndex,tempIdx);
+end;
+
+procedure TStoredLayer.Replace(ALayeredImage: TBGRALayeredBitmap);
+var
+  idxOrig: Integer;
+begin
+  if Assigned(FOriginalData) then
+  begin
+    FOriginalData.Position:= 0;
+    idxOrig := ALayeredImage.AddOriginalFromStream(FOriginalData, true);
+    if FOriginalKnown then
+    begin
+      ALayeredImage.LayerOriginalGuid[FIndex] := ALayeredImage.OriginalGuid[idxOrig];
+      ALayeredImage.LayerOriginalMatrix[FIndex] := FOriginalMatrix;
+      ALayeredImage.RenderLayerFromOriginal(FIndex, FOriginalDraft);
+    end else
+    begin
+      ALayeredImage.SetLayerBitmap(FIndex,GetBitmap,True);
+      ALayeredImage.LayerOffset[FIndex] := FInfo.Offset;
+      ALayeredImage.LayerOriginalGuid[FIndex] := ALayeredImage.OriginalGuid[idxOrig];
+      ALayeredImage.LayerOriginalMatrix[FIndex] := FOriginalMatrix;
+      ALayeredImage.LayerOriginalRenderStatus[FIndex] := FOriginalRenderStatus;
+    end;
+  end else
+    ALayeredImage.SetLayerBitmap(FIndex,GetBitmap,True);
+  ALayeredImage.RemoveUnusedOriginals;
+
+  ApplyLayerInfo(FInfo,ALayeredImage,FIndex);
 end;
 
 end.

+ 109 - 82
lazpaint/utool.pas

@@ -45,8 +45,11 @@ type
   protected
     FManager: TToolManager;
     FLastToolDrawingLayer: TBGRABitmap;
+    FBackupDrawingLayerBounds: TRect;
+    FBackupDrawingLayer: TBGRABitmap;
     function GetAction: TLayerAction; virtual;
     function GetIsSelectingTool: boolean; virtual; abstract;
+    function FixSelectionTransform: boolean; virtual;
     function DoToolDown(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF; rightBtn: boolean): TRect; virtual;
     function DoToolMove(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF): TRect; virtual;
     procedure DoToolMoveAfter(pt: TPoint; ptF: TPointF); virtual;
@@ -54,6 +57,7 @@ type
     procedure OnTryStop(sender: TCustomLayerAction); virtual;
     function SelectionMaxPointDistance: single;
     function GetStatusText: string; virtual;
+    function DoGetToolDrawingLayer: TBGRABitmap; virtual;
   public
     ToolUpdateNeeded: boolean;
     Cursor: TCursor;
@@ -79,7 +83,7 @@ type
     function ToolProvideCopy: boolean; virtual;
     function ToolProvideCut: boolean; virtual;
     function ToolProvidePaste: boolean; virtual;
-    function GetToolDrawingLayer: TBGRABitmap; virtual;
+    function GetToolDrawingLayer: TBGRABitmap;
     procedure RestoreBackupDrawingLayer;
     function GetBackupLayerIfExists: TBGRABitmap;
     function Render(VirtualScreen: TBGRABitmap; VirtualScreenWidth, VirtualScreenHeight: integer; BitmapToVirtualScreen: TBitmapToVirtualScreenFunction): TRect; virtual;
@@ -91,24 +95,13 @@ type
     property StatusText: string read GetStatusText;
   end;
 
-  { TGenericTransformTool }
-
-  TGenericTransformTool = class(TGenericTool)
-  protected
-    function GetKeepTransformOnDestroy: boolean; virtual; abstract;
-    procedure SetKeepTransformOnDestroy(AValue: boolean); virtual; abstract;
-  public
-    property KeepTransformOnDestroy: boolean read GetKeepTransformOnDestroy write SetKeepTransformOnDestroy;
-  end;
-
   { TReadonlyTool }
 
   TReadonlyTool = class(TGenericTool)
   protected
     function GetAction: TLayerAction; override;
     function GetIsSelectingTool: boolean; override;
-  public
-    function GetToolDrawingLayer: TBGRABitmap; override;
+    function DoGetToolDrawingLayer: TBGRABitmap; override;
   end;
 
   TToolClass = class of TGenericTool;
@@ -284,7 +277,8 @@ function ToolPopupMessageToStr(AMessage :TToolPopupMessage): string;
 
 implementation
 
-uses Types, ugraph, uscaledpi, LazPaintType, UCursors, BGRATextFX, ULoading, uresourcestrings;
+uses Types, ugraph, uscaledpi, LazPaintType, UCursors, BGRATextFX, ULoading, uresourcestrings,
+  BGRATransform;
 
 function StrToPaintToolType(const s: ansistring): TPaintToolType;
 var pt: TPaintToolType;
@@ -361,10 +355,10 @@ begin
   result := false;
 end;
 
-function TReadonlyTool.GetToolDrawingLayer: TBGRABitmap;
+function TReadonlyTool.DoGetToolDrawingLayer: TBGRABitmap;
 begin
-  if Manager.Image.SelectionEmpty or not assigned(Manager.Image.SelectionLayerReadonly) then
-    Result:= Manager.Image.SelectedImageLayerReadOnly
+  if Manager.Image.SelectionMaskEmpty or not assigned(Manager.Image.SelectionLayerReadonly) then
+    Result:= Manager.Image.CurrentLayerReadOnly
   else
     Result:= Manager.Image.SelectionLayerReadonly;
 end;
@@ -387,8 +381,8 @@ begin
   if IsSelectingTool or not Assigned(Manager.Image) then
     result := Point(0,0)
   else
-    if GetToolDrawingLayer = Manager.Image.SelectedImageLayerReadOnly then
-      result := Manager.Image.LayerOffset[Manager.Image.currentImageLayerIndex]
+    if GetToolDrawingLayer = Manager.Image.CurrentLayerReadOnly then
+      result := Manager.Image.LayerOffset[Manager.Image.CurrentLayerIndex]
     else
       result := Point(0,0);
 end;
@@ -398,16 +392,52 @@ begin
   result := '';
 end;
 
+function TGenericTool.DoGetToolDrawingLayer: TBGRABitmap;
+begin
+  if Action = nil then
+    result := nil
+  else if IsSelectingTool then
+  begin
+    Action.QuerySelection;
+    result := Action.CurrentSelection;
+    if result = nil then
+      raise exception.Create('Selection not created');
+  end
+  else
+    result := Action.DrawingLayer;
+end;
+
 function TGenericTool.GetAction: TLayerAction;
+var
+  layer: TBGRABitmap;
 begin
   if not Assigned(FAction) then
   begin
-    FAction := TLayerAction.Create(Manager.Image);
+    FAction := Manager.Image.CreateAction(not IsSelectingTool And Manager.Image.SelectionMaskEmpty);
     FAction.OnTryStop := @OnTryStop;
+    FAction.ChangeBoundsNotified:= true;
+    if IsSelectingTool or not Manager.Image.SelectionMaskEmpty then
+    begin
+      FAction.ApplySelectionTransform;
+      layer := GetToolDrawingLayer;
+      if Assigned(layer) then
+      begin
+        if layer = Manager.Image.SelectionMaskReadonly then
+          FBackupDrawingLayerBounds:= layer.GetImageBounds(cGreen)
+        else
+          FBackupDrawingLayerBounds:= layer.GetImageBounds;
+        FBackupDrawingLayer := layer.GetPart(FBackupDrawingLayerBounds) as TBGRABitmap;
+      end;
+    end;
   end;
   result := FAction;
 end;
 
+function TGenericTool.FixSelectionTransform: boolean;
+begin
+  result:= true;
+end;
+
 function TGenericTool.DoToolDown(toolDest: TBGRABitmap; pt: TPoint;
   ptF: TPointF; rightBtn: boolean): TRect;
 begin
@@ -517,7 +547,6 @@ end;
 function TGenericTool.ToolDown(X, Y: single; rightBtn: boolean): TRect;
 var
   toolDest: TBGRABitmap;
-  pt: TPoint;
   ptF: TPointF;
 begin
   result := EmptyRect;
@@ -526,23 +555,24 @@ begin
   toolDest.JoinStyle := Manager.ToolJoinStyle;
   toolDest.LineCap := Manager.ToolLineCap;
   toolDest.PenStyle := Manager.ToolPenStyle;
-  if toolDest = Manager.Image.SelectedImageLayerReadOnly then
-  begin
-    x -= LayerOffset.x;
-    y -= LayerOffset.y;
-  end;
-  pt := Point(round(x),round(y));
   ptF := PointF(x,y);
-  result := DoToolDown(toolDest,pt,ptF,rightBtn);
+  if toolDest = Manager.Image.CurrentLayerReadOnly then
+  begin
+    ptF.x -= LayerOffset.x;
+    ptF.y -= LayerOffset.y;
+  end else if FixSelectionTransform and ((toolDest = Manager.Image.SelectionMaskReadonly)
+    or (toolDest = Manager.Image.SelectionLayerReadonly)) and
+      IsAffineMatrixInversible(Manager.Image.SelectionTransform) then
+    ptF := AffineMatrixInverse(Manager.Image.SelectionTransform)*ptF;
+
+  result := DoToolDown(toolDest,ptF.Round,ptF,rightBtn);
 end;
 
 function TGenericTool.ToolMove(X, Y: single): TRect;
 var
   toolDest: TBGRABitmap;
-  pt: TPoint;
   ptF: TPointF;
 begin
-  pt := Point(round(x),round(y));
   ptF := PointF(x,y);
   Manager.ToolCurrentCursorPos := ptF;
   result := EmptyRect;
@@ -551,14 +581,16 @@ begin
   toolDest.JoinStyle := Manager.ToolJoinStyle;
   toolDest.LineCap := Manager.ToolLineCap;
   toolDest.PenStyle := Manager.ToolPenStyle;
-  if toolDest = Manager.Image.SelectedImageLayerReadOnly then
+  if toolDest = Manager.Image.CurrentLayerReadOnly then
   begin
-    x -= LayerOffset.x;
-    y -= LayerOffset.y;
-    pt := Point(round(x),round(y));
-    ptF := PointF(x,y);
-  end;
-  result := DoToolMove(toolDest,pt,ptF);
+    ptF.x -= LayerOffset.x;
+    ptF.y -= LayerOffset.y;
+  end else if FixSelectionTransform and ((toolDest = Manager.Image.SelectionMaskReadonly)
+    or (toolDest = Manager.Image.SelectionLayerReadonly)) and
+      IsAffineMatrixInversible(Manager.Image.SelectionTransform) then
+    ptF := AffineMatrixInverse(Manager.Image.SelectionTransform)*ptF;
+
+  result := DoToolMove(toolDest,ptF.Round,ptF);
 end;
 
 procedure TGenericTool.ToolMoveAfter(X, Y: single);
@@ -632,32 +664,35 @@ end;
 
 function TGenericTool.GetToolDrawingLayer: TBGRABitmap;
 begin
-  if Action = nil then
-  begin
-    result := nil;
-  end else
-  if IsSelectingTool then
-  begin
-    Action.QuerySelection;
-    result := Action.CurrentSelection;
-    if result = nil then
-      raise exception.Create('Selection not created');
-  end
-  else
-  begin
-    result := Action.DrawingLayer;
-  end;
+  result := DoGetToolDrawingLayer;
   FLastToolDrawingLayer := result;
 end;
 
 procedure TGenericTool.RestoreBackupDrawingLayer;
+var
+  layer: TBGRABitmap;
 begin
   if Assigned(FAction) then
   begin
-    if IsSelectingTool then
-      Action.RestoreSelection
-    else
-      Action.RestoreDrawingLayer;
+    if Assigned(FBackupDrawingLayer) then
+    begin
+      layer:= GetToolDrawingLayer;
+      if Assigned(layer) then
+      begin
+        if layer = Manager.Image.SelectionMaskReadonly then
+          layer.Fill(BGRABlack)
+        else
+          layer.FillTransparent;
+        layer.PutImage(FBackupDrawingLayerBounds.Left,FBackupDrawingLayerBounds.Top, FBackupDrawingLayer, dmSet);
+        Action.NotifyChange(layer, rect(0,0,layer.Width,layer.Height));
+      end;
+    end else
+    begin
+      if IsSelectingTool then
+        Action.RestoreSelectionMask
+      else
+        Action.RestoreDrawingLayer;
+    end;
   end;
 end;
 
@@ -741,12 +776,17 @@ procedure TToolManager.NotifyImageOrSelectionChanged(ALayer: TBGRABitmap; ARect:
 begin
   if (CurrentTool <> nil) and not IsRectEmpty(ARect) then
   begin
-    if CurrentTool.IsSelectingTool then
-      Image.SelectionMayChange(ARect)
-    else if ALayer <> nil then
-      Image.LayerMayChange(ALayer, ARect)
-    else
-      Image.ImageMayChange(AddLayerOffset(ARect))
+    if Assigned(CurrentTool.FAction) then
+      if not IsOnlyRenderChange(ARect) then
+        CurrentTool.FAction.NotifyChange(ALayer, ARect);
+
+    if ALayer = nil then
+    begin
+      if ALayer = Image.CurrentLayerReadOnly then
+        Image.ImageMayChange(AddLayerOffset(ARect))
+      else
+        Image.LayerMayChange(ALayer, ARect);
+    end
   end;
 end;
 
@@ -1031,30 +1071,15 @@ end;
 procedure TToolManager.InternalSetCurrentToolType(tool: TPaintToolType);
 var showPenwidth, showShape, showLineCap, showJoinStyle, showSplineStyle, showEraserOption, showTolerance, showGradient, showDeformation,
     showText, showPhong, showAltitude, showPerspective, showColor, showTexture, showBrush: boolean;
-    newTool: TGenericTool;
 begin
   if (tool <> FCurrentToolType) or (FCurrentTool=nil) then
   begin
-    if Assigned(FCurrentTool) and (FCurrentTool is TGenericTransformTool) then
-    begin
-      if PaintTools[tool] <> nil then
-        newTool := PaintTools[tool].Create(self)
-      else
-        newTool := nil;
-
-      if Assigned(newTool) and (newTool is TGenericTransformTool) then
-        TGenericTransformTool(FCurrentTool).KeepTransformOnDestroy := true;
-      FreeAndNil(FCurrentTool);
+    FreeAndNil(FCurrentTool);
+    if PaintTools[tool] <> nil then
+      FCurrentTool := PaintTools[tool].Create(self)
+    else
+      FCurrentTool := nil;
 
-      FCurrentTool := newTool;
-    end else
-    begin
-      FreeAndNil(FCurrentTool);
-      if PaintTools[tool] <> nil then
-        FCurrentTool := PaintTools[tool].Create(self)
-      else
-        FCurrentTool := nil;
-    end;
     FCurrentToolType:= tool;
   end;
 
@@ -1252,6 +1277,7 @@ function TToolManager.ToolDown(X, Y: single; ARightBtn: boolean;
 var changed: TRect;
 begin
   SetPressure(APressure);
+  Image.DraftOriginal := true;
   if ToolCanBeUsed then
     changed := currentTool.ToolDown(X,Y,ARightBtn)
   else
@@ -1377,6 +1403,7 @@ end;
 function TToolManager.ToolUp: boolean;
 var changed: TRect;
 begin
+  Image.DraftOriginal := false;
   if ToolCanBeUsed then
     changed := currentTool.ToolUp
   else

+ 39 - 67
lazpaint/utoolbasic.pas

@@ -16,6 +16,7 @@ type
   protected
     handMoving: boolean;
     handOrigin: TPoint;
+    function FixSelectionTransform: boolean; override;
     function DoToolDown({%H-}toolDest: TBGRABitmap; pt: TPoint; {%H-}ptF: TPointF;
       {%H-}rightBtn: boolean): TRect; override;
     function DoToolMove({%H-}toolDest: TBGRABitmap; pt: TPoint; {%H-}ptF: TPointF): TRect; override;
@@ -46,7 +47,6 @@ type
     penOrigin: TPointF;
     penColor: TBGRAPixel;
     snapToPixel: boolean;
-    function GetAction: TLayerAction; override;
     function GetIsSelectingTool: boolean; override;
     function StartDrawing(toolDest: TBGRABitmap; ptF: TPointF; rightBtn: boolean): TRect; virtual;
     function ContinueDrawing(toolDest: TBGRABitmap; originF, destF: TPointF): TRect; virtual;
@@ -80,7 +80,6 @@ type
     swapedColor: boolean;
     rectDrawing,afterRectDrawing: boolean;
     rectOrigin, rectDest: TPointF;
-    previousRect: TRect;
     rectMovingPoint,rectMovingCenterPoint: boolean;
     rectMovingPointValueDiff: TPointF;
     rectMovingPointClick: TPointF;
@@ -88,7 +87,6 @@ type
     rectMovingX,rectMovingY: PSingle;
     lastMousePos: TPointF;
     squareConstraint: boolean;
-    function GetAction: TLayerAction; override;
     function GetFillColor: TBGRAPixel; virtual;
     function GetPenColor: TBGRAPixel; virtual;
     function GetIsSelectingTool: boolean; override;
@@ -175,6 +173,7 @@ begin
    if Manager.ToolOptionDrawShape then
    begin
      result := GetShapeBounds([PointF(rectOrigin.x-rx,rectOrigin.y-ry),PointF(rectOrigin.x+rx,rectOrigin.y+ry)],Manager.ToolPenWidth);
+     toolDest.ClipRect := result;
      if Manager.ToolPenStyle = psSolid then
      begin
        if not Manager.ToolOptionFillShape and (Manager.GetToolTextureAfterAlpha <> nil) then
@@ -197,6 +196,7 @@ begin
    end else
    begin
      result := GetShapeBounds([PointF(rectOrigin.x-rx,rectOrigin.y-ry),PointF(rectOrigin.x+rx,rectOrigin.y+ry)],1);
+     toolDest.ClipRect := result;
    end;
    if Manager.ToolOptionFillShape then
      if (rx>0) and (ry>0) then
@@ -208,6 +208,7 @@ begin
      end;
    multi.Draw(toolDest);
    multi.Free;
+   toolDest.NoClip;
 end;
 
 function TToolEllipse.RoundCoordinate(ptF: TPointF): TPointF;
@@ -257,21 +258,27 @@ begin
     if rectDest.X > rectOrigin.X then sx := 1 else sx := -1;
     if rectDest.Y > rectOrigin.Y then sy := 1 else sy := -1;
     result := GetShapeBounds([PointF(rectOrigin.x,rectOrigin.y),PointF(rectDest.x,rectDest.y)],1);
+    toolDest.ClipRect := result;
     if Manager.GetToolTextureAfterAlpha <> nil then
       toolDest.FillRectAntialias(rectOrigin.X-0.5*sx,rectOrigin.Y-0.5*sy,rectDest.X+0.5*sx,rectDest.Y+0.5*sy,Manager.GetToolTextureAfterAlpha) else
       toolDest.FillRectAntialias(rectOrigin.X-0.5*sx,rectOrigin.Y-0.5*sy,rectDest.X+0.5*sx,rectDest.Y+0.5*sy,fillColor);
+    toolDest.NoClip;
   end else
   if Manager.ToolOptionDrawShape and not Manager.ToolOptionFillShape then
   begin
     result := GetShapeBounds([PointF(rectOrigin.x,rectOrigin.y),PointF(rectDest.x,rectDest.y)],Manager.ToolPenWidth);
+    toolDest.ClipRect := result;
     if Manager.GetToolTextureAfterAlpha <> nil then
       toolDest.RectangleAntialias(rectOrigin.X,rectOrigin.Y,rectDest.X,rectDest.Y,Manager.GetToolTextureAfterAlpha,Manager.ToolPenWidth) else
-      toolDest.RectangleAntialias(rectOrigin.X,rectOrigin.Y,rectDest.X,rectDest.Y,penColor,Manager.ToolPenWidth)
+      toolDest.RectangleAntialias(rectOrigin.X,rectOrigin.Y,rectDest.X,rectDest.Y,penColor,Manager.ToolPenWidth);
+    toolDest.NoClip;
   end else
   if Manager.ToolOptionDrawShape and Manager.ToolOptionFillShape then
   begin
     result := GetShapeBounds([PointF(rectOrigin.x,rectOrigin.y),PointF(rectDest.x,rectDest.y)],Manager.ToolPenWidth);
+    toolDest.ClipRect := result;
     toolDest.RectangleAntialias(rectOrigin.X,rectOrigin.Y,rectDest.X,rectDest.Y,penColor,Manager.ToolPenWidth,fillColor);
+    toolDest.NoClip;
   end else
     result := EmptyRect;
 end;
@@ -293,12 +300,6 @@ end;
 
 { TToolRectangular }
 
-function TToolRectangular.GetAction: TLayerAction;
-begin
-  Result:=inherited GetAction;
-  Result.AllChangesNotified := true;
-end;
-
 function TToolRectangular.GetFillColor: TBGRAPixel;
 begin
   if swapedColor then
@@ -429,13 +430,12 @@ begin
     rectOrigin := RoundCoordinate(ptF);
     rectDest := RoundCoordinate(ptF);
     PrepareDrawing(rightBtn);
-    previousRect := EmptyRect;
   end;
 end;
 
 function TToolRectangular.DoToolMove(toolDest: TBGRABitmap; pt: TPoint;
   ptF: TPointF): TRect;
-var currentRect: TRect;
+var
   delta: TPointF;
 begin
   result := EmptyRect;
@@ -457,19 +457,13 @@ begin
       rectDest += delta;
     end;
     ApplyConstraint(rectMovingX,rectMovingY);
-    currentRect := FinishShape(toolDest);
-    Action.NotifyChange(toolDest, currentRect);
-    result := RectUnion(previousRect,currentRect);
-    previousRect := currentRect;
+    result := FinishShape(toolDest);
   end else
   if rectDrawing and (rectDest <> RoundCoordinate(ptF)) then
   begin
     rectDest := RoundCoordinate(ptF);
     ApplyConstraint(@rectDest.X,@rectDest.Y);
-    currentRect := UpdateShape(toolDest);
-    Action.NotifyChange(toolDest, currentRect);
-    result := RectUnion(previousRect,currentRect);
-    previousRect := currentRect;
+    result := UpdateShape(toolDest);
   end;
   UpdateCursor(ptF);
 end;
@@ -480,7 +474,6 @@ begin
 end;
 
 function TToolRectangular.ToolUp: TRect;
-var currentRect: TRect;
 begin
   if rectMovingPoint then
   begin
@@ -492,17 +485,9 @@ begin
   end else
   if rectDrawing then
   begin
-    currentRect := ValidateDrawing;
-    if IsOnlyRenderChange(previousRect) then
-    begin
-      if IsRectEmpty(currentRect) then
-        result := OnlyRenderChange
-      else
-        result := currentRect;
-    end
-    else
-      result := RectUnion(previousRect,currentRect);
-    previousRect := currentRect;
+    result := ValidateDrawing;
+    if IsRectEmpty(result) then
+      result := OnlyRenderChange;
     afterRectDrawing := true;
     UpdateCursor(lastMousePos);
   end
@@ -575,7 +560,6 @@ begin
   begin
     rectDrawing := false;
     result := FinishShape(GetToolDrawingLayer);
-    Action.NotifyChange(GetToolDrawingLayer, result);
     afterRectDrawing:= true;
   end else
     result := EmptyRect;
@@ -621,22 +605,12 @@ begin
 end;
 
 function TToolRectangular.DoToolUpdate(toolDest: TBGRABitmap): TRect;
-var currentRect: TRect;
 begin
   if rectDrawing then
-  begin
-    currentRect := UpdateShape(toolDest);
-    Action.NotifyChange(toolDest, currentRect);
-    result := RectUnion(previousRect,currentRect);
-    previousRect := currentRect;
-  end else
+    result := UpdateShape(toolDest)
+  else
   if afterRectDrawing then
-  begin
-    currentRect := FinishShape(toolDest);
-    Action.NotifyChange(toolDest, currentRect);
-    result := RectUnion(previousRect,currentRect);
-    previousRect := currentRect;
-  end
+    result := FinishShape(toolDest)
   else
     result := EmptyRect;
 end;
@@ -717,10 +691,9 @@ end;
 constructor TToolRectangular.Create(AManager: TToolManager);
 begin
   inherited Create(AManager);
-  Action.AllChangesNotified:= true;
+  Action.ChangeBoundsNotified:= true;
   rectMovingPoint := false;
   afterRectDrawing:= false;
-  previousRect := EmptyRect;
   squareConstraint := false;
 end;
 
@@ -766,7 +739,6 @@ begin
         Manager.ToolPenWidth,True);
       mask.ScanOffset := Point(-result.left,-result.top);
       areaCopy.ScanOffset := Point(-result.left,-result.top);
-      toolDest.ScanOffset := Point(0,0);
       toolDest.CrossFade(result,toolDest,areaCopy,mask,dmSet);
       mask.Free;
       areaCopy.Free;
@@ -782,11 +754,12 @@ begin
     end
     else
     begin
-      toolDest.EraseLineAntialias(ptF.X,ptF.Y,ptF.X,ptF.Y,round(Manager.ToolEraserAlpha*Manager.ToolPressure),Manager.ToolPenWidth,True);
       result := GetShapeBounds([ptF],Manager.ToolPenWidth);
+      toolDest.ClipRect := result;
+      toolDest.EraseLineAntialias(ptF.X,ptF.Y,ptF.X,ptF.Y,round(Manager.ToolEraserAlpha*Manager.ToolPressure),Manager.ToolPenWidth,True);
+      toolDest.NoClip;
     end;
   end;
-  Action.NotifyChange(toolDest, result);
 end;
 
 function TToolErase.ContinueDrawing(toolDest: TBGRABitmap; originF,
@@ -807,7 +780,6 @@ begin
         Manager.ToolPenWidth,false);
       mask.ScanOffset := Point(-result.left,-result.top);
       areaCopy.ScanOffset := Point(-result.left,-result.top);
-      toolDest.ScanOffset := Point(0,0);
       toolDest.CrossFade(result,toolDest,areaCopy,mask,dmSet);
       mask.Free;
       areaCopy.Free;
@@ -822,17 +794,10 @@ begin
     toolDest.EraseLineAntialias(destF.X,destF.Y,originF.X,originF.Y,round(Manager.ToolEraserAlpha*Manager.ToolPressure),Manager.ToolPenWidth,False);
     result := GetShapeBounds([destF,originF],Manager.ToolPenWidth);
   end;
-  Action.NotifyChange(toolDest, result);
 end;
 
 { TToolPen }
 
-function TToolPen.GetAction: TLayerAction;
-begin
-  Result:=inherited GetAction;
-  result.AllChangesNotified:= true;
-end;
-
 function TToolPen.GetIsSelectingTool: boolean;
 begin
   Result:= false;
@@ -851,13 +816,14 @@ begin
     result := rect(ix,iy,ix+1,iy+1);
   end else
   begin
+    result := GetShapeBounds([ptF],Manager.ToolPenWidth);
+    toolDest.ClipRect := result;
      if Manager.GetToolTextureAfterAlpha <> nil then
        toolDest.FillEllipseAntialias(ptF.X,ptF.Y,Manager.ToolPenWidth/2,Manager.ToolPenWidth/2,Manager.GetToolTextureAfterAlpha)
      else
        toolDest.FillEllipseAntialias(ptF.X,ptF.Y,Manager.ToolPenWidth/2,Manager.ToolPenWidth/2,Manager.ApplyPressure(penColor));
-     result := GetShapeBounds([ptF],Manager.ToolPenWidth);
+     toolDest.NoClip;
   end;
-  Action.NotifyChange(toolDest, result);
 end;
 
 function TToolPen.ContinueDrawing(toolDest: TBGRABitmap; originF, destF: TPointF): TRect;
@@ -868,13 +834,14 @@ begin
     result := GetShapeBounds([destF,originF],1);
   end else
   begin
-     if Manager.GetToolTextureAfterAlpha <> nil then
-       toolDest.DrawLineAntialias(destF.X,destF.Y,originF.X,originF.Y,Manager.GetToolTextureAfterAlpha,Manager.ToolPenWidth,False)
-     else
-       toolDest.DrawLineAntialias(destF.X,destF.Y,originF.X,originF.Y,Manager.ApplyPressure(penColor),Manager.ToolPenWidth,False);
-     result := GetShapeBounds([destF,originF],Manager.ToolPenWidth+1);
+    result := GetShapeBounds([destF,originF],Manager.ToolPenWidth+1);
+    toolDest.ClipRect := result;
+    if Manager.GetToolTextureAfterAlpha <> nil then
+      toolDest.DrawLineAntialias(destF.X,destF.Y,originF.X,originF.Y,Manager.GetToolTextureAfterAlpha,Manager.ToolPenWidth,False)
+    else
+      toolDest.DrawLineAntialias(destF.X,destF.Y,originF.X,originF.Y,Manager.ApplyPressure(penColor),Manager.ToolPenWidth,False);
+    toolDest.NoClip;
   end;
-  Action.NotifyChange(toolDest, result);
 end;
 
 function TToolPen.DoToolDown(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF;
@@ -986,6 +953,11 @@ end;
 
 { TToolHand }
 
+function TToolHand.FixSelectionTransform: boolean;
+begin
+  Result:= false;
+end;
+
 function TToolHand.DoToolDown(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF;
   rightBtn: boolean): TRect;
 begin

+ 1 - 2
lazpaint/utoolbrush.pas

@@ -77,7 +77,7 @@ begin
   if definingSource then
   begin
     sourcePosition := Point(round(x),round(y));
-    sourceLayerId := Manager.Image.LayerId[Manager.Image.currentImageLayerIndex];
+    sourceLayerId := Manager.Image.LayerId[Manager.Image.CurrentLayerIndex];
     sourcePositionRelative:= false;
     result := OnlyRenderChange;
   end else
@@ -270,7 +270,6 @@ begin
       end;
     end;
   end;
-  Action.NotifyChange(toolDest,result);
 end;
 
 function TToolGenericBrush.GetBrushAlpha(AAlpha: byte): byte;

+ 30 - 31
lazpaint/utooldeformationgrid.pas

@@ -44,7 +44,7 @@ type
   TToolTextureMapping = class(TGenericTool)
   private
     class var FHintShowed: boolean;
-    FCurrentBounds,FMergedBounds: TRect;
+    FCurrentBounds: TRect;
     FAdaptedTexture: TBGRABitmap;
     FCanReadaptTexture: boolean;
     FHighQuality: boolean;
@@ -75,7 +75,6 @@ type
     procedure PrepareBackground({%H-}toolDest: TBGRABitmap; AFirstTime: boolean); virtual;
     function DefaultTextureCenter: TPointF; virtual;
     function DoToolUpdate({%H-}toolDest: TBGRABitmap): TRect; override;
-    function GetAction: TLayerAction; override;
     function GetStatusText: string; override;
   public
     constructor Create(AManager: TToolManager); override;
@@ -124,13 +123,18 @@ end;
 
 procedure TToolLayerMapping.PrepareBackground(toolDest: TBGRABitmap;
   AFirstTime: boolean);
+var
+  r: TRect;
 begin
-  toolDest.FillTransparent;
   if not FAlreadyDrawnOnce then
   begin
     FAlreadyDrawnOnce := true;
-    Manager.Image.LayerMayChangeCompletely(toolDest);
-  end;
+    r := toolDest.GetImageBounds;
+  end else
+    r := FCurrentBounds;
+
+  toolDest.FillRect(r, BGRAPixelTransparent, dmSet);
+  Action.NotifyChange(toolDest, r);
 end;
 
 function TToolLayerMapping.GetTexture: TBGRABitmap;
@@ -182,6 +186,7 @@ begin
       quadDefined:= true;
       PrepareBackground(GetToolDrawingLayer, True);
       DrawQuad;
+      Action.NotifyChange(GetToolDrawingLayer, FCurrentBounds);
     end;
   end;
 end;
@@ -198,10 +203,8 @@ begin
       DrawQuad;
       FCanReadaptTexture:= false;
       FHighQuality := false;
-      Manager.Image.LayerMayChange(GetToolDrawingLayer,FMergedBounds);
+      Action.NotifyChange(GetToolDrawingLayer, FCurrentBounds);
     end;
-    Action.AllChangesNotified := true;
-    Action.NotifyChange(GetToolDrawingLayer, FMergedBounds);
     ValidateAction;
     quadDefined := false;
     quad := nil;
@@ -210,7 +213,7 @@ end;
 
 procedure TToolTextureMapping.DrawQuad;
 const OversampleQuality = 2;
-var previousBounds: TRect;
+var
   tex: TBGRABitmap;
   persp: TBGRAPerspectiveScannerTransform;
   dest: TBGRABitmap;
@@ -233,23 +236,27 @@ begin
     tex := GetAdaptedTexture;
     if tex <> nil then
     begin
-      previousBounds := FCurrentBounds;
+
+      if Manager.ToolPerspectiveRepeat then
+        FCurrentBounds := rect(0,0,Manager.Image.Width,Manager.Image.Height)
+      else
+        FCurrentBounds := GetShapeBounds([quad[0],quad[1],quad[2],quad[3]],1);
 
       if FHighQuality then
       begin
-        dest := TBGRABitmap.Create(Manager.Image.Width*OversampleQuality,Manager.Image.Height*OversampleQuality);
+        dest := TBGRABitmap.Create(FCurrentBounds.Width*OversampleQuality,FCurrentBounds.Height*OversampleQuality);
         setlength(quadHQ, length(quad));
-        for i := 0 to high(quad) do quadHQ[i] := (quad[i]+PointF(0.5,0.5))*OversampleQuality - PointF(0.5,0.5);
+        for i := 0 to high(quad) do quadHQ[i] := (quad[i]+PointF(0.5,0.5))*OversampleQuality - PointF(0.5,0.5) - PointF(FCurrentBounds.TopLeft)*OversampleQuality;
       end
       else
       begin
         dest := GetToolDrawingLayer;
         quadHQ := quad;
+        dest.ClipRect := FCurrentBounds;
       end;
 
       if Manager.ToolPerspectiveRepeat then
       begin
-        FCurrentBounds := rect(0,0,Manager.Image.Width,Manager.Image.Height);
         persp := TBGRAPerspectiveScannerTransform.Create(tex,[PointF(-0.5,-0.5),PointF(tex.Width-0.5,-0.5),
           PointF(tex.Width-0.5,tex.Height-0.5),PointF(-0.5,tex.Height-0.5)],quadHQ);
         persp.IncludeOppositePlane := Manager.ToolPerspectiveTwoPlanes;
@@ -257,7 +264,6 @@ begin
         persp.Free;
       end else
       begin
-        FCurrentBounds := GetShapeBounds([quad[0],quad[1],quad[2],quad[3]],1);
         dest.FillQuadPerspectiveMappingAntialias(quadHQ[0],quadHQ[1],quadHQ[2],quadHQ[3],tex,PointF(-0.5,-0.5),PointF(tex.Width-0.5,-0.5),
           PointF(tex.Width-0.5,tex.Height-0.5),PointF(-0.5,tex.Height-0.5), rect(0,0,tex.Width,tex.Height));
       end;
@@ -266,14 +272,14 @@ begin
       begin
         BGRAReplace(dest, dest.Resample(dest.Width div OversampleQuality, dest.Height div OversampleQuality,rmSimpleStretch));
         BGRAReplace(dest, dest.FilterSharpen(96/256));
-        GetToolDrawingLayer.PutImage(0,0,dest,dmDrawWithTransparency);
+        GetToolDrawingLayer.PutImage(FCurrentBounds.Left,FCurrentBounds.Top,dest,dmDrawWithTransparency);
         FreeAndNil(dest);
-      end;
-      Action.NotifyChange(dest, FCurrentBounds);
-      FMergedBounds := RectUnion(previousBounds,FCurrentBounds);
-      Manager.Image.LayerMayChange(GetToolDrawingLayer,FMergedBounds);
+      end else
+        dest.NoClip;
     end;
-  end;
+  end
+  else
+    FCurrentBounds := EmptyRect;
 end;
 
 function TToolTextureMapping.GetAdaptedTexture: TBGRABitmap;
@@ -463,7 +469,7 @@ begin
       quad[quadMovingIndex] := SnapIfNecessary(quadMovingDelta + ptF);
     PrepareBackground(toolDest,False);
     DrawQuad;
-    result := FMergedBounds;
+    result := FCurrentBounds;
   end;
 end;
 
@@ -520,18 +526,12 @@ begin
   begin
     PrepareBackground(GetToolDrawingLayer,False);
     DrawQuad;
-    result := FMergedBounds;
+    result := FCurrentBounds;
   end
     else
       result := EmptyRect;
 end;
 
-function TToolTextureMapping.GetAction: TLayerAction;
-begin
-  Result:=inherited GetAction;
-  result.AllChangesNotified:= true;
-end;
-
 function TToolTextureMapping.GetStatusText: string;
 var
   i: Integer;
@@ -665,7 +665,7 @@ begin
       FCanReadaptTexture:= true;
       DrawQuad;
       FCanReadaptTexture:= false;
-      result := FMergedBounds;
+      result := FCurrentBounds;
     end else
       result := EmptyRect;
     quadMoving := false;
@@ -706,6 +706,7 @@ end;
 
 destructor TToolTextureMapping.Destroy;
 begin
+  ValidateAction;
   FreeAndNil(FAdaptedTexture);
   inherited Destroy;
 end;
@@ -777,7 +778,6 @@ begin
   begin
     DeformationGrid := nil;
     DeformationGridTexCoord := nil;
-    Manager.Image.LayerMayChange(GetToolDrawingLayer,FMergedBounds);
     ValidateAction;
     DoingDeformation := false;
   end;
@@ -937,7 +937,6 @@ begin
     result := FMergedBounds;
   end;
   deformationOrigin := ptF;
-
 end;
 
 function TToolDeformationGrid.GetIsSelectingTool: boolean;

+ 1 - 1
lazpaint/utoolfloodfill.pas

@@ -156,7 +156,7 @@ begin
     if Manager.ToolFloodFillOptionProgressive then
       toolDest.FloodFill(pt.X,pt.Y,penColor,fmProgressive,Manager.ToolTolerance) else
         toolDest.FloodFill(pt.X,pt.Y,penColor,fmDrawWithTransparency,Manager.ToolTolerance);
-  Manager.Image.LayerMayChangeCompletely(toolDest);
+  Action.NotifyChange(toolDest, rect(0,0,toolDest.Width,toolDest.Height));
   ValidateAction;
   result := OnlyRenderChange;
 end;

+ 151 - 78
lazpaint/utoollayer.pas

@@ -5,7 +5,7 @@ unit UToolLayer;
 interface
 
 uses
-  Classes, SysUtils, UTool, BGRABitmap, BGRABitmapTypes, UImageType;
+  Classes, SysUtils, UTool, BGRABitmap, BGRABitmapTypes, UImageType, BGRATransform;
 
 type
   { TToolMoveLayer }
@@ -14,8 +14,9 @@ type
   protected
     handMoving: boolean;
     handOrigin: TPoint;
-    FOriginalLayerOffset: TPoint;
-    FOriginalLayerOffsetDefined: boolean;
+    FStartLayerOffset: TPoint;
+    FStartLayerMatrix: TAffineMatrix;
+    FStartLayerOffsetDefined: boolean;
     FLayerBounds: TRect;
     FLayerBoundsDefined: boolean;
     function GetIsSelectingTool: boolean; override;
@@ -24,10 +25,10 @@ type
     function DoToolMove({%H-}toolDest: TBGRABitmap; pt: TPoint; {%H-}ptF: TPointF): TRect; override;
     procedure DoToolMoveAfter(pt: TPoint; {%H-}ptF: TPointF); override;
     procedure OnTryStop({%H-}sender: TCustomLayerAction); override;
-    procedure ApplyMoveLayer;
+    function UseOriginal: boolean;
+    procedure NeedLayerBounds;
+    function DoGetToolDrawingLayer: TBGRABitmap; override;
   public
-    destructor Destroy; override;
-    function GetToolDrawingLayer: TBGRABitmap; override;
     function ToolUp: TRect; override;
     function ToolKeyDown(var key: Word): TRect; override;
     function Render(VirtualScreen: TBGRABitmap; {%H-}VirtualScreenWidth,
@@ -43,9 +44,11 @@ type
     function GetOriginalLayerBounds: TRect;
     function GetRotationCenter: TPointF;
     procedure SetRotationCenter(AValue: TPointF);
+    function UseOriginal: boolean;
   protected
-    FOriginalLayerBounds: TRect;
-    FOriginalLayerBoundsDefined: boolean;
+    FInitialOriginalMatrix: TAffineMatrix;
+    FInitialLayerBounds: TRect;
+    FInitialLayerBoundsDefined: boolean;
     FRotationCenter: TPointF;
     FRotationCenterDefined: boolean;
     FFilter: TResampleFilter;
@@ -63,9 +66,12 @@ type
     function DoToolMove({%H-}toolDest: TBGRABitmap; {%H-}pt: TPoint; ptF: TPointF): TRect;
       override;
     function UpdateRotation: TRect;
+    procedure CancelRotation;
+    procedure ValidateRotation;
     property RotationCenter: TPointF read GetRotationCenter write SetRotationCenter;
     property OriginalLayerBounds: TRect read GetOriginalLayerBounds;
     procedure OnTryStop({%H-}sender: TCustomLayerAction); override;
+    function DoGetToolDrawingLayer: TBGRABitmap; override;
   public
     constructor Create(AManager: TToolManager); override;
     destructor Destroy; override;
@@ -93,12 +99,12 @@ end;
 
 function TToolRotateLayer.GetOriginalLayerBounds: TRect;
 begin
-  if not FOriginalLayerBoundsDefined then
+  if not FInitialLayerBoundsDefined then
   begin
-    FOriginalLayerBounds := GetToolDrawingLayer.GetImageBounds;
-    FOriginalLayerBoundsDefined := true;
+    FInitialLayerBounds := GetToolDrawingLayer.GetImageBounds;
+    FInitialLayerBoundsDefined := true;
   end;
-  result := FOriginalLayerBounds;
+  result := FInitialLayerBounds;
 end;
 
 function TToolRotateLayer.GetRotationCenter: TPointF;
@@ -108,8 +114,12 @@ begin
     if IsRectEmpty(OriginalLayerBounds) then
       FRotationCenter := PointF(Manager.Image.Width/2 - 0.5,Manager.Image.Height/2 - 0.5)
     else
-    with OriginalLayerBounds do
-      FRotationCenter := PointF((Left+Right)/2 - 0.5, (Top+Bottom)/2 - 0.5);
+    begin
+      with OriginalLayerBounds do
+        FRotationCenter := PointF((Left+Right)/2 - 0.5, (Top+Bottom)/2 - 0.5);
+      with Manager.Image.LayerOffset[Manager.Image.CurrentLayerIndex] do
+        FRotationCenter += PointF(X,Y);
+    end;
     FRotationCenterDefined := true;
   end;
   result := FRotationCenter;
@@ -120,9 +130,19 @@ begin
   FRotationCenter := AValue;
 end;
 
+function TToolRotateLayer.UseOriginal: boolean;
+begin
+  with Manager.Image do
+    result := LayerOriginalDefined[CurrentLayerIndex] and
+              LayerOriginalKnown[CurrentLayerIndex];
+end;
+
 function TToolRotateLayer.DoToolDown(toolDest: TBGRABitmap; pt: TPoint;
   ptF: TPointF; rightBtn: boolean): TRect;
 begin
+  with Manager.Image.LayerOffset[Manager.Image.CurrentLayerIndex] do
+    ptF += PointF(X,Y);
+
   if not FRotating and not rightBtn then
   begin
     FRotating := true;
@@ -148,7 +168,10 @@ end;
 function TToolRotateLayer.DoToolMove(toolDest: TBGRABitmap; pt: TPoint;
   ptF: TPointF): TRect;
 var angleDiff: single;
+  ofs: TPoint;
 begin
+  with Manager.Image.LayerOffset[Manager.Image.CurrentLayerIndex] do
+    ptF += PointF(X,Y);
   if FRotating then
   begin
     angleDiff := ComputeAngle(ptF.X-RotationCenter.X,ptF.Y-RotationCenter.Y)-
@@ -174,22 +197,47 @@ begin
   FPreviousRotationCenter := RotationCenter;
   FPreviousFilter := FFilter;
   result := EmptyRect;
-  if not FLastUpdateRectDefined then
+
+  if UseOriginal then
   begin
-    GetToolDrawingLayer.FillTransparent;
-    result := rect(0,0,GetToolDrawingLayer.Width,GetToolDrawingLayer.Height);
+    Manager.Image.LayerOriginalMatrix[Manager.Image.CurrentLayerIndex] :=
+      AffineMatrixTranslation(RotationCenter.X,RotationCenter.Y)*
+      AffineMatrixRotationDeg(FActualAngle)*
+      AffineMatrixTranslation(-RotationCenter.X,-RotationCenter.Y)*
+      FInitialOriginalMatrix;
   end else
-  if not IsRectEmpty(FLastUpdateRect) then
   begin
-    GetToolDrawingLayer.FillRect(FLastUpdateRect,BGRAPixelTransparent,dmSet);
-    result := FLastUpdateRect;
+    if not FLastUpdateRectDefined then
+    begin
+      GetToolDrawingLayer.FillTransparent;
+      result := rect(0,0,GetToolDrawingLayer.Width,GetToolDrawingLayer.Height);
+    end else
+    if not IsRectEmpty(FLastUpdateRect) then
+    begin
+      GetToolDrawingLayer.FillRect(FLastUpdateRect,BGRAPixelTransparent,dmSet);
+      result := FLastUpdateRect;
+    end;
+    FLastUpdateRect := GetToolDrawingLayer.GetImageAngleBounds(0,0,Action.BackupDrawingLayer,FActualAngle,RotationCenter.X,RotationCenter.Y,True);
+    FLastUpdateRectDefined:= true;
+    GetToolDrawingLayer.ComputeImageAngleAxes(0,0,Action.BackupDrawingLayer.Width,Action.BackupDrawingLayer.Height,FActualAngle,RotationCenter.X,RotationCenter.Y,True,
+    origin,haxis,vaxis);
+    GetToolDrawingLayer.PutImageAffine(origin,haxis,vaxis,Action.BackupDrawingLayer,FLastUpdateRect,FFilter,dmSet,255);
+    result := RectUnion(result,FLastUpdateRect);
   end;
-  FLastUpdateRect := GetToolDrawingLayer.GetImageAngleBounds(0,0,Action.BackupDrawingLayer,FActualAngle,RotationCenter.X,RotationCenter.Y,True);
-  FLastUpdateRectDefined:= true;
-  GetToolDrawingLayer.ComputeImageAngleAxes(0,0,Action.BackupDrawingLayer.Width,Action.BackupDrawingLayer.Height,FActualAngle,RotationCenter.X,RotationCenter.Y,True,
-  origin,haxis,vaxis);
-  GetToolDrawingLayer.PutImageAffine(origin,haxis,vaxis,Action.BackupDrawingLayer,FLastUpdateRect,FFilter,dmSet,255);
-  result := RectUnion(result,FLastUpdateRect);
+end;
+
+procedure TToolRotateLayer.CancelRotation;
+begin
+  if UseOriginal then
+    Manager.Image.LayerOriginalMatrix[Manager.Image.CurrentLayerIndex] := FInitialOriginalMatrix
+  else
+    CancelActionPartially;
+  Manager.QueryExitTool;
+end;
+
+procedure TToolRotateLayer.ValidateRotation;
+begin
+  Manager.QueryExitTool;
 end;
 
 procedure TToolRotateLayer.OnTryStop(sender: TCustomLayerAction);
@@ -197,22 +245,32 @@ begin
   //nothing
 end;
 
+function TToolRotateLayer.DoGetToolDrawingLayer: TBGRABitmap;
+begin
+  if UseOriginal then
+    Result:= Manager.Image.CurrentLayerReadOnly   //do not modify layer data directly and ignore selection
+  else
+    Result:= Action.SelectedImageLayer;
+end;
+
 constructor TToolRotateLayer.Create(AManager: TToolManager);
 begin
   inherited Create(AManager);
   FAngle:= 0;
   FPreviousActualAngle := 0;
   FCtrlDown:= false;
-  FOriginalLayerBoundsDefined:= false;
+  FInitialLayerBoundsDefined:= false;
   FRotationCenterDefined := false;
   FLastUpdateRectDefined:= false;
   FFilter := rfCosine;
   FPreviousFilter := FFilter;
+  FInitialOriginalMatrix := Manager.Image.LayerOriginalMatrix[Manager.Image.CurrentLayerIndex];
 end;
 
 destructor TToolRotateLayer.Destroy;
 begin
-  ValidateActionPartially;
+  if not UseOriginal then
+    ValidateAction;
   inherited Destroy;
 end;
 
@@ -231,17 +289,15 @@ begin
   end else
   if Key = VK_RETURN then
   begin
-    if FActualAngle = 0 then CancelActionPartially
-     else ValidateActionPartially;
+    if FActualAngle = 0 then CancelRotation
+    else ValidateRotation;
     result := OnlyRenderChange;
-    manager.QueryExitTool;
     key := 0;
   end else
   if Key = VK_ESCAPE then
   begin
-    CancelActionPartially;
+    CancelRotation;
     result := OnlyRenderChange;
-    manager.QueryExitTool;
     key := 0;
   end else
     result := EmptyRect;
@@ -272,7 +328,8 @@ function TToolRotateLayer.Render(VirtualScreen: TBGRABitmap;
   VirtualScreenWidth, VirtualScreenHeight: integer;
   BitmapToVirtualScreen: TBitmapToVirtualScreenFunction): TRect;
 begin
-  Result:= NicePoint(VirtualScreen,BitmapToVirtualScreen(RotationCenter));
+  with Manager.Image.LayerOffset[Manager.Image.CurrentLayerIndex] do
+    Result:= NicePoint(VirtualScreen,BitmapToVirtualScreen(RotationCenter-PointF(X,Y)));
 end;
 
 function TToolRotateLayer.GetIsSelectingTool: boolean;
@@ -296,17 +353,13 @@ begin
   begin
     handMoving := true;
     handOrigin := pt;
-    if not FOriginalLayerOffsetDefined then
+    if not FStartLayerOffsetDefined then
     begin
-      FOriginalLayerOffsetDefined := true;
-      idx := Manager.Image.currentImageLayerIndex;
-      if not FLayerBoundsDefined then
-      begin
-        FLayerBounds := Manager.Image.LayerBitmap[idx].GetImageBounds;
-        FLayerBoundsDefined := true;
-      end;
-      FOriginalLayerOffset := Manager.Image.LayerOffset[idx];
-      GetAction;
+      FStartLayerOffsetDefined := true;
+      idx := Manager.Image.CurrentLayerIndex;
+      NeedLayerBounds;
+      FStartLayerOffset := Manager.Image.LayerOffset[idx];
+      FStartLayerMatrix := Manager.Image.LayerOriginalMatrix[idx];
     end;
   end;
 end;
@@ -318,11 +371,19 @@ var idx: integer;
 begin
   if handMoving and ((handOrigin.X <> pt.X) or (handOrigin.Y <> pt.Y)) then
   begin
-    idx := Manager.Image.currentImageLayerIndex;
-    prev := Manager.Image.LayerOffset[idx];
-    Manager.Image.SetLayerOffset(idx, Point(prev.X+pt.X-HandOrigin.X,
-                                       prev.Y+pt.Y-HandOrigin.Y), FLayerBounds);
-    result := OnlyRenderChange;
+    idx := Manager.Image.CurrentLayerIndex;
+    if UseOriginal then
+    begin
+      Manager.Image.LayerOriginalMatrix[idx] :=
+          AffineMatrixTranslation(pt.X-HandOrigin.X,pt.Y-HandOrigin.Y)*Manager.Image.LayerOriginalMatrix[idx];
+      result := OnlyRenderChange;
+    end else
+    begin
+      prev := Manager.Image.LayerOffset[idx];
+      Manager.Image.SetLayerOffset(idx, Point(prev.X+pt.X-HandOrigin.X,
+                                         prev.Y+pt.Y-HandOrigin.Y), FLayerBounds);
+      result := OnlyRenderChange;
+    end;
   end else
     result := EmptyRect;
 end;
@@ -337,29 +398,33 @@ begin
   //nothing
 end;
 
-procedure TToolMoveLayer.ApplyMoveLayer;
-var finalOffset: TPoint;
-  idx: integer;
+function TToolMoveLayer.UseOriginal: boolean;
 begin
-  if FOriginalLayerOffsetDefined then
-  begin
-    idx := Manager.Image.currentImageLayerIndex;
-    finalOffset := Manager.Image.LayerOffset[idx];
-    Manager.Image.SetLayerOffset(idx, FOriginalLayerOffset, FLayerBounds);
-    Manager.Image.ApplyLayerOffset(finalOffset.x,finalOffset.y);
-    FOriginalLayerOffsetDefined := false;
-  end;
+  with Manager.Image do
+    result := LayerOriginalDefined[CurrentLayerIndex] and
+              LayerOriginalKnown[CurrentLayerIndex];
 end;
 
-destructor TToolMoveLayer.Destroy;
+procedure TToolMoveLayer.NeedLayerBounds;
+var
+  idx: Integer;
 begin
-  ApplyMoveLayer;
-  inherited Destroy;
+  idx := Manager.Image.CurrentLayerIndex;
+  if not FLayerBoundsDefined then
+  begin
+    if UseOriginal then
+      FLayerBounds := Manager.Image.LayerOriginal[idx].GetRenderBounds(
+                        Rect(-maxLongint div 2,-maxLongint div 2,maxLongint div 2,maxLongint div 2),
+                        AffineMatrixLinear(Manager.Image.LayerOriginalMatrix[idx]))
+    else
+      FLayerBounds := Manager.Image.LayerBitmap[idx].GetImageBounds;
+    FLayerBoundsDefined := true;
+  end;
 end;
 
-function TToolMoveLayer.GetToolDrawingLayer: TBGRABitmap;
+function TToolMoveLayer.DoGetToolDrawingLayer: TBGRABitmap;
 begin
-  Result:= Manager.Image.SelectedImageLayerReadOnly;   //do not create a selection layer
+  Result:= Manager.Image.CurrentLayerReadOnly;   //do not modify layer data directly and ignore selection
 end;
 
 function TToolMoveLayer.ToolUp: TRect;
@@ -379,10 +444,13 @@ begin
   end
   else if key = VK_ESCAPE then
   begin
-    if FOriginalLayerOffsetDefined then
+    if FStartLayerOffsetDefined then
     begin
-      idx := Manager.Image.currentImageLayerIndex;
-      Manager.Image.LayerOffset[idx] := FOriginalLayerOffset;
+      idx := Manager.Image.CurrentLayerIndex;
+      if UseOriginal then
+        Manager.Image.LayerOriginalMatrix[idx] := FStartLayerMatrix
+      else
+        Manager.Image.SetLayerOffset(idx, FStartLayerOffset, FLayerBounds);
       result := OnlyRenderChange;
     end else
       result := EmptyRect;
@@ -396,19 +464,24 @@ function TToolMoveLayer.Render(VirtualScreen: TBGRABitmap; VirtualScreenWidth,
   VirtualScreenHeight: integer;
   BitmapToVirtualScreen: TBitmapToVirtualScreenFunction): TRect;
 var pt1,pt2:TPoint;
-  ptF1,ptF2: TPointF;
+  ptF1,ptF2, ofs: TPointF;
   penWidth,i,idx: integer;
+  m: BGRABitmapTypes.TAffineMatrix;
 begin
-  idx := Manager.Image.currentImageLayerIndex;
-  if not FLayerBoundsDefined then
-  begin
-    Action;
-    FLayerBounds := Manager.Image.LayerBitmap[idx].GetImageBounds;
-    FLayerBoundsDefined := true;
-  end;
+  idx := Manager.Image.CurrentLayerIndex;
+  NeedLayerBounds;
 
-  ptF1 := BitmapToVirtualScreen(PointF(FLayerBounds.Left-0.5,FLayerBounds.Top-0.5));
-  ptF2 := BitmapToVirtualScreen(PointF(FLayerBounds.Right-0.5,FLayerBounds.Bottom-0.5));
+  if UseOriginal then
+  begin
+    m := Manager.Image.LayerOriginalMatrix[idx];
+    ofs := PointF(m[1,3],m[2,3]);
+    with Manager.Image.LayerOffset[idx] do
+      ofs := ofs-PointF(x,y);
+  end
+  else
+    ofs := PointF(0,0);
+  ptF1 := BitmapToVirtualScreen(PointF(FLayerBounds.Left-0.5,FLayerBounds.Top-0.5)+ofs);
+  ptF2 := BitmapToVirtualScreen(PointF(FLayerBounds.Right-0.5,FLayerBounds.Bottom-0.5)+ofs);
   pt1 := point(round(ptF1.x),round(ptF1.y));
   pt2 := point(round(ptF2.X)-1,round(ptF2.Y)-1);
 

+ 1 - 4
lazpaint/utoolphong.pas

@@ -138,10 +138,7 @@ begin
     rightBtnDown := true;
     Manager.ToolLightPosition := pt;
     if afterRectDrawing then
-    begin
-      result := FinishShape(toolDest);
-      Action.NotifyChange(toolDest, result);
-    end
+      result := FinishShape(toolDest)
     else
       result := OnlyRenderChange;
     exit;

+ 4 - 20
lazpaint/utoolpolygon.pas

@@ -17,7 +17,6 @@ type
   TToolGenericPolygon = class(TGenericTool)
   strict private
     swapColorKey: boolean;
-    FCurrentBounds: TRect;
     lastMousePos: TPointF;
   protected
     class var HintShowed: boolean;
@@ -49,7 +48,6 @@ type
     function GetPenColor: TBGRAPixel; virtual;
     function FinishHandDrawing: TRect;
     function AddLastClickedPoint: boolean; virtual;
-    function GetAction: TLayerAction; override;
     procedure OnFinishHandDrawing; virtual;
     procedure OnAddPoint({%H-}AIndex: integer); virtual;
     procedure OnDeletePoint({%H-}AIndex: integer); virtual;
@@ -535,12 +533,6 @@ begin
   result := false;
 end;
 
-function TToolGenericPolygon.GetAction: TLayerAction;
-begin
-  Result:=inherited GetAction;
-  result.AllChangesNotified := true;
-end;
-
 procedure TToolGenericPolygon.OnFinishHandDrawing;
 begin
 
@@ -609,10 +601,10 @@ var r: TRect;
 begin
   if not FAfterHandDrawing then
   begin
+    RestoreBackupDrawingLayer;
     r := FinalPolygonView(toolDest);
     Action.NotifyChange(toolDest,r);
     setlength(polygonPoints,0);
-    Manager.Image.LayerMayChange(toolDest,r);
     FAfterHandDrawing:= True;
   end else
   begin
@@ -624,23 +616,17 @@ begin
 end;
 
 function TToolGenericPolygon.UpdatePolygonView(toolDest: TBGRABitmap): TRect;
-var
-   previousBounds : TRect;
 begin
-  previousBounds := FCurrentBounds;
   RestoreBackupDrawingLayer;
   if FAfterHandDrawing then
-    FCurrentBounds := FinalPolygonView(toolDest)
+    result := FinalPolygonView(toolDest)
   else
-    FCurrentBounds := HandDrawingPolygonView(toolDest);
-  Action.NotifyChange(toolDest, FCurrentBounds);
-  result := RectUnion(previousBounds,FCurrentBounds);
+    result := HandDrawingPolygonView(toolDest);
 end;
 
 procedure TToolGenericPolygon.StartPolygon(rightBtn: boolean);
 begin
   swapedColor := rightBtn;
-  FCurrentBounds := EmptyRect;
 end;
 
 function TToolGenericPolygon.SnapToPixelEdge: boolean;
@@ -887,9 +873,7 @@ begin
         begin
           RestoreBackupDrawingLayer;
           polygonPoints := nil;
-          result := FCurrentBounds;
-          if IsRectEmpty(result) then result := OnlyRenderChange;
-          FCurrentBounds := EmptyRect;
+          result := OnlyRenderChange;
         end else
           result := FinishHandDrawing
       end

+ 24 - 75
lazpaint/utoolselect.pas

@@ -5,7 +5,8 @@ unit UToolSelect;
 interface
 
 uses
-  Classes, SysUtils, Graphics, utool, utoolpolygon, utoolbasic, BGRABitmapTypes, BGRABitmap;
+  Classes, SysUtils, Graphics, utool, utoolpolygon, utoolbasic, BGRABitmapTypes, BGRABitmap,
+  ULayerAction;
 
 type
 
@@ -78,26 +79,22 @@ type
     function Render(VirtualScreen: TBGRABitmap; {%H-}VirtualScreenWidth, {%H-}VirtualScreenHeight: integer; BitmapToVirtualScreen: TBitmapToVirtualScreenFunction): TRect; override;
   end;
 
-  { TToolTransformSelection }
+  { TTransformSelectionTool }
 
-  TToolTransformSelection = class(TGenericTransformTool)
+  TTransformSelectionTool = class(TGenericTool)
   protected
-    FKeepTransformOnDestroy: boolean;
-    function GetKeepTransformOnDestroy: boolean; override;
-    procedure SetKeepTransformOnDestroy(AValue: boolean); override;
-  public
-    procedure SelectionTransformChange;
-    constructor Create(AManager: TToolManager); override;
-    destructor Destroy; override;
+    function GetIsSelectingTool: boolean; override;
+    function GetAction: TLayerAction; override;
+    function FixSelectionTransform: boolean; override;
+    function DoGetToolDrawingLayer: TBGRABitmap; override;
   end;
 
   { TToolMoveSelection }
 
-  TToolMoveSelection = class(TToolTransformSelection)
+  TToolMoveSelection = class(TTransformSelectionTool)
   protected
     handMoving: boolean;
     handOrigin: TPoint;
-    function GetIsSelectingTool: boolean; override;
     function DoToolDown({%H-}toolDest: TBGRABitmap; pt: TPoint; {%H-}ptF: TPointF;
       {%H-}rightBtn: boolean): TRect; override;
     function DoToolMove({%H-}toolDest: TBGRABitmap; pt: TPoint; {%H-}ptF: TPointF): TRect; override;
@@ -109,7 +106,7 @@ type
 
   { TToolRotateSelection }
 
-  TToolRotateSelection = class(TToolTransformSelection)
+  TToolRotateSelection = class(TTransformSelectionTool)
   protected
     class var HintShowed: boolean;
     handMoving: boolean;
@@ -119,7 +116,6 @@ type
     FOriginalTransform: TAffineMatrix;
     FCurrentAngle: single;
     FCurrentCenter: TPointF;
-    function GetIsSelectingTool: boolean; override;
     function DoToolDown({%H-}toolDest: TBGRABitmap; {%H-}pt: TPoint; ptF: TPointF;
       rightBtn: boolean): TRect; override;
     function DoToolMove({%H-}toolDest: TBGRABitmap; {%H-}pt: TPoint; ptF: TPointF): TRect; override;
@@ -138,72 +134,45 @@ implementation
 
 uses types, ugraph, LCLType, LazPaintType, Math, BGRATransform, BGRAPath;
 
-{ TToolTransformSelection }
-
-function TToolTransformSelection.GetKeepTransformOnDestroy: boolean;
-begin
-  result := FKeepTransformOnDestroy;
-end;
+{ TTransformSelectionTool }
 
-procedure TToolTransformSelection.SetKeepTransformOnDestroy(AValue: boolean);
+function TTransformSelectionTool.GetIsSelectingTool: boolean;
 begin
-  FKeepTransformOnDestroy:= AValue;
+  result := true;
 end;
 
-procedure TToolTransformSelection.SelectionTransformChange;
-var selectionChangeRect: TRect;
+function TTransformSelectionTool.GetAction: TLayerAction;
 begin
-  selectionChangeRect := Manager.Image.TransformedSelectionBounds;
-  if not Manager.Image.SelectionLayerIsEmpty then
-    Manager.Image.ImageMayChange(selectionChangeRect,False);
-  if not IsRectEmpty(selectionChangeRect) then
-  begin
-    InflateRect(selectionChangeRect,1,1);
-    Manager.Image.RenderMayChange(selectionChangeRect,true);
-  end;
+  Result:= nil;
 end;
 
-constructor TToolTransformSelection.Create(AManager: TToolManager);
+function TTransformSelectionTool.FixSelectionTransform: boolean;
 begin
-  inherited Create(AManager);
-  FKeepTransformOnDestroy:= False;
+  Result:= false;
 end;
 
-destructor TToolTransformSelection.Destroy;
+function TTransformSelectionTool.DoGetToolDrawingLayer: TBGRABitmap;
 begin
-  if not FKeepTransformOnDestroy and not IsAffineMatrixIdentity(Manager.Image.SelectionTransform) then
-  begin
-    Action.ApplySelectionTransform;
-    ValidateAction;
-  end;
-  inherited Destroy;
+  result := Manager.Image.SelectionMaskReadonly;
 end;
 
 { TToolRotateSelection }
 
-function TToolRotateSelection.GetIsSelectingTool: boolean;
-begin
-  Result:= true;
-end;
-
 function TToolRotateSelection.DoToolDown(toolDest: TBGRABitmap; pt: TPoint;
   ptF: TPointF; rightBtn: boolean): TRect;
 begin
   result := EmptyRect;
-  if not handMoving and not Manager.Image.SelectionEmpty then
+  if not handMoving and not Manager.Image.SelectionMaskEmpty then
   begin
     if rightBtn then
     begin
       if FCurrentAngle <> 0 then
       begin
-        SelectionTransformChange;
         FCurrentAngle := 0;
         FCurrentCenter := ptF;
         UpdateTransform;
-        SelectionTransformChange;
       end else
       begin
-        FCurrentAngle := 0;
         FCurrentCenter := ptF;
         UpdateTransform;
       end;
@@ -227,7 +196,6 @@ begin
   end;
   if handMoving and ((handOrigin.X <> ptF.X) or (handOrigin.Y <> ptF.Y)) then
   begin
-    SelectionTransformChange;
     angleDiff := ComputeAngle(ptF.X-FCurrentCenter.X,ptF.Y-FCurrentCenter.Y)-
                  ComputeAngle(handOrigin.X-FCurrentCenter.X,handOrigin.Y-FCurrentCenter.Y);
     if snapRotate then
@@ -238,7 +206,6 @@ begin
      else
        FCurrentAngle := FCurrentAngle + angleDiff;
     UpdateTransform;
-    SelectionTransformChange;
     handOrigin := ptF;
     result := OnlyRenderChange;
   end else
@@ -260,7 +227,7 @@ end;
 constructor TToolRotateSelection.Create(AManager: TToolManager);
 begin
   inherited Create(AManager);
-  FCurrentCenter := Manager.Image.SelectionTransform * Manager.Image.GetSelectionCenter;
+  FCurrentCenter := Manager.Image.SelectionTransform * Manager.Image.GetSelectionMaskCenter;
   FOriginalTransform := Manager.Image.SelectionTransform;
   FCurrentAngle := 0;
 end;
@@ -277,10 +244,8 @@ begin
 
       if handMoving then
       begin
-        SelectionTransformChange;
         FCurrentAngle := round(snapAngle/15)*15;
         UpdateTransform;
-        SelectionTransformChange;
         result := OnlyRenderChange;
       end;
     end;
@@ -289,13 +254,6 @@ begin
   if key = VK_ESCAPE then
   begin
     if FCurrentAngle <> 0 then
-    begin
-      SelectionTransformChange;
-      FCurrentAngle := 0;
-      UpdateTransform;
-      SelectionTransformChange;
-      result := OnlyRenderChange;
-    end else
     begin
       FCurrentAngle := 0;
       UpdateTransform;
@@ -337,11 +295,6 @@ end;
 
 { TToolMoveSelection }
 
-function TToolMoveSelection.GetIsSelectingTool: boolean;
-begin
-  result := true;
-end;
-
 function TToolMoveSelection.DoToolDown(toolDest: TBGRABitmap; pt: TPoint;
   ptF: TPointF; rightBtn: boolean): TRect;
 begin
@@ -360,11 +313,9 @@ begin
   result := EmptyRect;
   if handMoving and ((handOrigin.X <> pt.X) or (handOrigin.Y <> pt.Y)) then
   begin
-    SelectionTransformChange;
     dx := pt.X-HandOrigin.X;
     dy := pt.Y-HandOrigin.Y;
     Manager.Image.SelectionTransform := AffineMatrixTranslation(dx,dy) * Manager.Image.SelectionTransform;
-    SelectionTransformChange;
     result := OnlyRenderChange;
   end;
 end;
@@ -653,7 +604,6 @@ begin
   if rightBtn then penColor := BGRABlack else penColor := BGRAWhite;
   toolDest.DrawLineAntialias(ptF.X,ptF.Y,ptF.X,ptF.Y,penColor,Manager.ToolPenWidth,True);
   result := GetShapeBounds([ptF],Manager.ToolPenWidth+1);
-  Action.NotifyChange(toolDest, result);
 end;
 
 function TToolSelectionPen.ContinueDrawing(toolDest: TBGRABitmap; originF,
@@ -661,7 +611,6 @@ function TToolSelectionPen.ContinueDrawing(toolDest: TBGRABitmap; originF,
 begin
   toolDest.DrawLineAntialias(destF.X,destF.Y,originF.X,originF.Y,penColor,Manager.ToolPenWidth,False);
   result := GetShapeBounds([destF,originF],Manager.ToolPenWidth+1);
-  Action.NotifyChange(toolDest, result);
 end;
 
 { TToolMagicWand }
@@ -681,10 +630,10 @@ begin
     exit;
   end;
   if rightBtn then penColor := BGRABlack else penColor := BGRAWhite;
-  Manager.Image.SelectedImageLayerReadOnly.ParallelFloodFill(pt.X,pt.Y,toolDest,penColor,fmDrawWithTransparency,Manager.ToolTolerance);
-  Manager.Image.SelectionMayChangeCompletely;
+  Manager.Image.CurrentLayerReadOnly.ParallelFloodFill(pt.X,pt.Y,toolDest,penColor,fmDrawWithTransparency,Manager.ToolTolerance);
+  result := rect(0,0,toolDest.Width,toolDest.Height);
+  Action.NotifyChange(toolDest, result);
   ValidateAction;
-  result := rect(0,0,Manager.Image.Width,Manager.Image.Height);
 end;
 
 { TToolSelectPoly }

+ 2 - 11
lazpaint/utooltext.pas

@@ -6,7 +6,7 @@ interface
 
 uses
   Types, Classes, SysUtils, utool, utoolbasic, LCLType, Graphics, BGRABitmap, BGRABitmapTypes, BGRATextFX,
-  BGRAGradients, ULayerAction;
+  BGRAGradients;
 
 type
 
@@ -39,7 +39,6 @@ type
     function TextSize: TSize;
     function TextSizeOf(s: string): TSize;
     procedure UpdateTextFX;
-    function GetAction: TLayerAction; override;
     function DeleteSelection: boolean;
     procedure SelectionFollows;
     procedure GetCaretPos(AIndex:integer; out caretTop,caretBottom:TPointF);
@@ -186,9 +185,7 @@ begin
   end else
     currentRect := EmptyRect;
 
-  Action.NotifyChange(toolDest,currentRect);
-  result := RectUnion(currentRect,previousRect);
-  previousRect := currentRect;
+  result := currentRect;
   if IsRectEmpty(result) then result := OnlyRenderChange; //no text but update caret
 end;
 
@@ -271,12 +268,6 @@ begin
   shader.LightPositionZ := Manager.ToolLightAltitude;
 end;
 
-function TToolText.GetAction: TLayerAction;
-begin
-  Result:=inherited GetAction;
-  result.AllChangesNotified:= true;
-end;
-
 function TToolText.DeleteSelection: boolean;
 begin
   if FTextCharPos <> FTextSelStart then

+ 9 - 25
lazpaintcontrols/lctoolbars.pas

@@ -20,7 +20,6 @@ function AddToolbarButton(AToolbar: TToolbar; ACaption: string; AImageIndex: int
           AOnClick: TNotifyEvent; ATag: PtrInt = 0): TToolButton;
 function AddToolbarUpDown(AToolbar: TToolbar; ACaption: string; AMin,AMax,AValue: Integer; AOnChange: TTrackBarUpDownChangeEvent): TBCTrackbarUpdown;
 procedure AddToolbarControl(AToolbar: TToolbar; AControl: TControl);
-function GetResourceStream(AFilename: string): TLazarusResourceStream;
 function GetResourceString(AFilename: string): string;
 procedure LoadToolbarImage(AImages: TImageList; AIndex: integer; AFilename: string);
 
@@ -98,41 +97,26 @@ begin
   AToolbar.ButtonHeight:= AImages.Height+4;
 end;
 
-function GetResourceStream(AFilename: string): TLazarusResourceStream;
-var
-  ext: RawByteString;
-begin
-  ext := UpperCase(ExtractFileExt(AFilename));
-  if (ext<>'') and (ext[1]='.') then Delete(ext,1,1);
-  result := TLazarusResourceStream.Create(ChangeFileExt(AFilename,''), pchar(ext));
-end;
-
 function GetResourceString(AFilename: string): string;
 var
-  res: TLResource;
-  ext: RawByteString;
+  strStream: TStringStream;
+  resStream: TStream;
 begin
-  ext := UpperCase(ExtractFileExt(AFilename));
-  if (ext<>'') and (ext[1]='.') then Delete(ext,1,1);
-  res := LazarusResources.Find(ChangeFileExt(AFilename,''), ext);
-  if assigned(res) then result:= res.Value else result:= '';
+  resStream := BGRAResource.GetResourceStream(AFilename);
+  strStream := TStringStream.Create('');
+  strStream.CopyFrom(resStream, resStream.Size);
+  resStream.Free;
+  result:= strStream.DataString;
+  strStream.Free;
 end;
 
 procedure LoadToolbarImage(AImages: TImageList; AIndex: integer; AFilename: string);
 var
   iconImg: TBGRALazPaintImage;
   iconFlat: TBGRABitmap;
-  res: TLazarusResourceStream;
-  mem: TMemoryStream;
 begin
   iconImg := TBGRALazPaintImage.Create;
-  res := GetResourceStream(AFilename);
-  mem:= TMemoryStream.Create;
-  res.SaveToStream(mem);
-  res.Free;
-  mem.Position:= 0;
-  iconImg.LoadFromStream(mem);
-  mem.Free;
+  iconImg.LoadFromResource(AFilename);
   iconImg.Resample(AImages.Width,AImages.Height,rmFineResample,rfBestQuality);
   iconFlat := TBGRABitmap.Create(iconImg.Width,iconImg.Height);
   iconImg.Draw(iconFlat,0,0);

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff