瀏覽代碼

Merge pull request #150 from bgrabitmap/dev-lazpaint

Dev lazpaint
circular17 5 年之前
父節點
當前提交
c2d0169e00

+ 70 - 92
lazpaint/dialog/ubrowseimages.pas

@@ -10,9 +10,6 @@ uses
   BGRAAnimatedGif, UMySLV, LazPaintType, Masks, LCLType, UFileSystem,
   UImagePreview;
 
-const
-  MaxIconCacheCount = 512;
-
 type
 
   { TFBrowseImages }
@@ -87,6 +84,7 @@ type
     FChosenImage: TImageEntry;
     FPreview: TImagePreview;
     FComputeIconCurrentItem: integer;
+    FCacheComputeIconIndexes: array of integer;
     FPreviewFilename: string;
     FInShowPreview,FInHidePreview: boolean;
     FSavedDetailsViewWidth: integer;
@@ -174,10 +172,8 @@ uses BGRAThumbnail, BGRAPaintNet, BGRAOpenRaster, BGRAReadLzp,
     Types, UResourceStrings,
     UConfig, bgrareadjpeg, FPReadJPEG,
     UFileExtensions, BGRAUTF8, LazFileUtils,
-    UGraph, URaw, UDarkTheme, ShellCtrls;
-
-var
-  IconCache: TStringList;
+    UGraph, URaw, UDarkTheme, ShellCtrls,
+    UIconCache;
 
 { TFBrowseImages }
 
@@ -340,6 +336,9 @@ end;
 
 procedure TFBrowseImages.FormHide(Sender: TObject);
 begin
+  FCacheComputeIconIndexes := nil;
+  StopCaching(true);
+
   FLastBigIcon := (ShellListView1.ViewStyle = vsIcon);
   if not IsSaveDialog then FFilename:= FPreviewFilename;
   Timer1.Enabled := false;
@@ -491,6 +490,8 @@ end;
 procedure TFBrowseImages.ShellListView1OnSort(Sender: TObject);
 begin
   FComputeIconCurrentItem := 0;
+  FCacheComputeIconIndexes := nil;
+  StopCaching;
 end;
 
 procedure TFBrowseImages.ShellListView1OnFormatType(Sender: Tobject;
@@ -512,98 +513,83 @@ begin
 end;
 
 procedure TFBrowseImages.Timer1Timer(Sender: TObject);
-var i: integer;
-  iconRect,shellRect:TRect;
-  endDate: TDateTime;
-
-  function DetermineIcon(i: integer): boolean;
-  var itemPath,cacheName,dummyCaption: string;
-    cacheIndex: integer;
-    found: boolean;
-    mem: TMemoryStream;
-    s: TStream;
+const MaxCacheComputeCount = 10;
+var
+  bmpIcon: TBGRABitmap;
+  iconRect, shellRect:TRect;
+  i,j,cacheComputeCount: Integer;
+  newFilenames: array of string;
+  newLastModifications: array of TDateTime;
+begin
+  Timer1.Enabled:= false;
+  if FPreview.Filename <> FPreviewFilename then
+    UpdatePreview
+  else
+    FPreview.HandleTimer;
+
+  if not IsCacheBusy and (length(FCacheComputeIconIndexes) > 0) then
   begin
-    result := false;
-    if ShellListView1.GetItemImage(i) = FImageFileNotChecked then
+    //retrieve computed icons
+    for i := 0 to high(FCacheComputeIconIndexes) do
     begin
-      if ShellListView1.ItemIsFolder[i] then
-        ShellListView1.SetItemImage(i,FImageFolder,false)
-      else
+      j := FCacheComputeIconIndexes[i];
+      if ShellListView1.GetItemImage(j) = FImageFileNotChecked then
       begin
-        itemPath := ShellListView1.ItemFullName[i];
-        cacheName := itemPath+':'+FloatToStr(ShellListView1.ItemLastModification[i]);
-        cacheIndex := IconCache.IndexOf(cacheName);
-        if not Assigned(FBmpIcon) then FBmpIcon := TBGRABitmap.Create;
-        if cacheIndex <> -1 then
-        begin
-          TStream(IconCache.Objects[cacheIndex]).Position:= 0;
-          TBGRAReaderLazPaint.LoadRLEImage(TStream(IconCache.Objects[cacheIndex]),FBmpIcon,dummyCaption);
-          found := true;
-        end
+        bmpIcon := GetCachedIcon(ShellListView1.ItemFullName[j],
+                                 ShellListView1.ItemLastModification[j],
+                                 FImageFileUnkown);
+        if Assigned(bmpIcon) then
+          ShellListView1.SetItemImage(j, bmpIcon, bmpIcon <> FImageFileUnkown)
         else
-        begin
-          try
-            s := FileManager.CreateFileStream(itemPath, fmOpenRead or fmShareDenyWrite);
-            try
-              if IsRawFilename(itemPath) then
-              begin
-                found := GetRawStreamThumbnail(s,ShellListView1.LargeIconSize,ShellListView1.LargeIconSize, BGRAPixelTransparent, True, FBmpIcon) <> nil;
-              end else
-                found := GetStreamThumbnail(s,ShellListView1.LargeIconSize,ShellListView1.LargeIconSize, BGRAPixelTransparent, True, ExtractFileExt(itemPath), FBmpIcon) <> nil;
-            finally
-              s.Free;
-            end;
-          except
-            found := false;
-          end;
-          if found then
-          begin
-            if IconCache.Count >= MaxIconCacheCount then IconCache.Delete(0);
-            mem := TMemoryStream.Create;
-            TBGRAWriterLazPaint.WriteRLEImage(mem,FBmpIcon);
-            IconCache.AddObject(cacheName,mem);
-          end;
-        end;
-        if found then
-        begin
-          ShellListView1.SetItemImage(i,FBmpIcon.Duplicate as TBGRABitmap,True);
-        end else
-          ShellListView1.SetItemImage(i,FImageFileUnkown,False);
+          if j <  FComputeIconCurrentItem then
+            FComputeIconCurrentItem := j;
       end;
-      result := true;
     end;
+    FCacheComputeIconIndexes := nil;
   end;
 
-var someIconDone: boolean;
-
-begin
-  Timer1.Enabled:= false;
-  EndDate := Now + 50 / MSecsPerDay;
-  if FPreview.Filename <> FPreviewFilename then
-    UpdatePreview
-  else
-    FPreview.HandleTimer;
-  if FComputeIconCurrentItem < ShellListView1.ItemCount then
+  if not IsCacheBusy and (FComputeIconCurrentItem < ShellListView1.ItemCount) then
   begin
-    vsList.Cursor := crAppStart;
+    //queue icons to compute
+    setlength(FCacheComputeIconIndexes, MaxCacheComputeCount);
+    cacheComputeCount := 0;
+
+    //compute icons for visible items
     shellRect := rect(0,0,ShellListView1.Width,ShellListView1.Height);
-    someIconDone := false;
     for i := FComputeIconCurrentItem to ShellListView1.ItemCount-1 do
     if ShellListView1.GetItemImage(i) = FImageFileNotChecked then
-    If Now >= EndDate then break else
     begin
       iconRect := ShellListView1.ItemDisplayRect[i];
-      if IntersectRect(iconRect,iconRect,shellRect) then
-        if DetermineIcon(i) then someIconDone := true;
+      if IntersectRect(iconRect, iconRect, shellRect) then
+      begin
+        FCacheComputeIconIndexes[cacheComputeCount] := i;
+        inc(cacheComputeCount);
+        if cacheComputeCount = MaxCacheComputeCount then break;
+      end;
     end;
-    if not someIconDone then EndDate := Now + 50 / MSecsPerDay;
-    for i := FComputeIconCurrentItem to ShellListView1.ItemCount-1 do
-    If Now >= EndDate then break else
+
+    //compute icons in current display order
+    while (FComputeIconCurrentItem < ShellListView1.ItemCount-1)
+      and (cacheComputeCount < MaxCacheComputeCount) do
+    begin
+      if ShellListView1.GetItemImage(FComputeIconCurrentItem) = FImageFileNotChecked then
+      begin
+        FCacheComputeIconIndexes[cacheComputeCount] := FComputeIconCurrentItem;
+        inc(cacheComputeCount);
+      end;
+      inc(FComputeIconCurrentItem);
+    end;
+
+    setlength(FCacheComputeIconIndexes, cacheComputeCount);
+    setlength(newFilenames, cacheComputeCount);
+    setlength(newLastModifications, cacheComputeCount);
+    for i := 0 to cacheComputeCount-1 do
     begin
-      FComputeIconCurrentItem := i+1;
-      DetermineIcon(i);
+      j := FCacheComputeIconIndexes[i];
+      newFilenames[i] := ShellListView1.ItemFullName[j];
+      newLastModifications[i] := ShellListView1.ItemLastModification[j];
     end;
-    vsList.Cursor := crDefault;
+    AddToCache(newFilenames, newLastModifications, ShellListView1.LargeIconSize);
   end;
   vsList.SetBounds(vsList.Left, vsList.Top, Panel2.Width, Panel2.Height-Panel3.Height);
   ShellListView1.Update;
@@ -916,6 +902,8 @@ begin
         ShellListView1.SetItemImage(i,FImageFileNotChecked,false);
     end;
   FComputeIconCurrentItem := 0;
+  FCacheComputeIconIndexes := nil;
+  StopCaching;
 end;
 
 procedure TFBrowseImages.SelectCurrentDir;
@@ -1248,15 +1236,5 @@ begin
   FreeAndNil(FChosenImage.bmp);
 end;
 
-initialization
-
-IconCache := TStringList.Create;
-IconCache.CaseSensitive := true;
-IconCache.OwnsObjects := true;
-
-finalization
-
-IconCache.Free;
-
 end.
 

+ 1 - 1
lazpaint/image/uimage.pas

@@ -464,7 +464,7 @@ begin
   begin
     if (Width > 256) or (Height > 256) then
     begin
-      ShowMessage(rsNotReasonableFormat);
+      ShowMessage(rsNotReasonableFormat + ' (> 256x256)');
       result := false;
     end;
   end;

+ 7 - 2
lazpaint/lazpaint.lpi

@@ -24,9 +24,9 @@
       <UseVersionInfo Value="True"/>
       <MajorVersionNr Value="7"/>
       <MinorVersionNr Value="1"/>
+      <RevisionNr Value="2"/>
       <CharSet Value="04B0"/>
       <StringTable CompanyName="http://sourceforge.net/projects/lazpaint/" ProductName="LazPaint" InternalName="lazpaint" OriginalFilename="lazpaint.exe"/>
-      <RevisionNr Value="1"/>
     </VersionInfo>
     <BuildModes Count="8">
       <Item1 Name="Debug" Default="True"/>
@@ -350,7 +350,7 @@
         <PackageName Value="LCL"/>
       </Item5>
     </RequiredPackages>
-    <Units Count="105">
+    <Units Count="106">
       <Unit0>
         <Filename Value="lazpaint.lpr"/>
         <IsPartOfProject Value="True"/>
@@ -968,6 +968,11 @@
         <IsPartOfProject Value="True"/>
         <UnitName Value="UImageBackup"/>
       </Unit104>
+      <Unit105>
+        <Filename Value="uiconcache.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="UIconCache"/>
+      </Unit105>
     </Units>
   </ProjectOptions>
   <CompilerOptions>

+ 1 - 1
lazpaint/lazpaint.lpr

@@ -40,7 +40,7 @@ uses
   URainType, UFormRain, UPaletteToolbar, uselectionhighlight,
   UImagePreview, UPreviewDialog, UQuestion, UTiff, UImageView,
   UDarkTheme, URaw, UProcessAuto, UPython, UImageBackup, ULayerStackInterface,
-  UChooseColorInterface;
+  UChooseColorInterface, UIconCache;
 
 //sometimes LResources disappear in the uses clause
 

+ 1 - 1
lazpaint/lazpaintembeddedpack.lpk

@@ -26,7 +26,7 @@
         </Debugging>
       </Linking>
     </CompilerOptions>
-    <Version Major="7" Minor="1" Release="1"/>
+    <Version Major="7" Minor="1" Release="2"/>
     <Files Count="94">
       <Item1>
         <Filename Value="lazpaintinstance.pas"/>

+ 6 - 1
lazpaint/lazpaintmainform.pas

@@ -947,7 +947,12 @@ begin
   btnLeftDown := false;
   btnRightDown := false;
   btnMiddleDown:= false;
-  FTablet := TLazTablet.Create(self);
+  try
+    FTablet := TLazTablet.Create(self);
+  except
+    on ex: exception do
+      FTablet := nil;
+  end;
   spacePressed:= false;
   altPressed:= false;
   snapPressed:= false;

+ 1 - 1
lazpaint/lazpainttype.pas

@@ -10,7 +10,7 @@ uses
   {$IFDEF LINUX}, InterfaceBase{$ENDIF};
 
 const
-  LazPaintVersion = 7010100;
+  LazPaintVersion = 7010200;
 
   function LazPaintVersionStr: string;
 

+ 14 - 0
lazpaint/release/changelog

@@ -186,3 +186,17 @@ lazpaint (7.1) stable; urgency=low
 
 -- circular <[email protected]>  Fri, 10 Apr 2020 12:07:00 +0100
 
+lazpaint (7.1.2) stable; urgency=low
+
+  * installer: add scripts on Windows
+  * installer: add new extensions on Windows (oXo, cur, jpeg, tif, tga, webp, xpm)
+  * rendering: phong shape undo
+  * rendering: vector layer with selection and transform
+  * rendering: add font kerning for text shape
+  * file browser: load thumbnails in separate thread to prevent freeze
+  * file browser: generate less file extensions to avoid slowdown
+  * crash fixes: skip when matrix transform is invalid
+  * crash fixes: catch tablet initialization error
+
+-- circular <[email protected]>  Fri, 24 Apr 2020 14:19:00 +0100
+

+ 14 - 0
lazpaint/release/debian/linux32/DEBIAN/changelog

@@ -186,3 +186,17 @@ lazpaint (7.1) stable; urgency=low
 
 -- circular <[email protected]>  Fri, 10 Apr 2020 12:07:00 +0100
 
+lazpaint (7.1.2) stable; urgency=low
+
+  * installer: add scripts on Windows
+  * installer: add new extensions on Windows (oXo, cur, jpeg, tif, tga, webp, xpm)
+  * rendering: phong shape undo
+  * rendering: vector layer with selection and transform
+  * rendering: add font kerning for text shape
+  * file browser: load thumbnails in separate thread to prevent freeze
+  * file browser: generate less file extensions to avoid slowdown
+  * crash fixes: skip when matrix transform is invalid
+  * crash fixes: catch tablet initialization error
+
+-- circular <[email protected]>  Fri, 24 Apr 2020 14:19:00 +0100
+

+ 1 - 1
lazpaint/release/debian/linux32/DEBIAN/control

@@ -1,5 +1,5 @@
 Package: lazpaint
-Version: 7.1.1
+Version: 7.1.2
 Section: base
 Priority: optional
 Architecture: i386

+ 14 - 0
lazpaint/release/debian/linux64/DEBIAN/changelog

@@ -186,3 +186,17 @@ lazpaint (7.1) stable; urgency=low
 
 -- circular <[email protected]>  Fri, 10 Apr 2020 12:07:00 +0100
 
+lazpaint (7.1.2) stable; urgency=low
+
+  * installer: add scripts on Windows
+  * installer: add new extensions on Windows (oXo, cur, jpeg, tif, tga, webp, xpm)
+  * rendering: phong shape undo
+  * rendering: vector layer with selection and transform
+  * rendering: add font kerning for text shape
+  * file browser: load thumbnails in separate thread to prevent freeze
+  * file browser: generate less file extensions to avoid slowdown
+  * crash fixes: skip when matrix transform is invalid
+  * crash fixes: catch tablet initialization error
+
+-- circular <[email protected]>  Fri, 24 Apr 2020 14:19:00 +0100
+

+ 1 - 1
lazpaint/release/debian/linux64/DEBIAN/control

@@ -1,5 +1,5 @@
 Package: lazpaint
-Version: 7.1.1
+Version: 7.1.2
 Section: base
 Priority: optional
 Architecture: amd64

+ 2 - 2
lazpaint/release/macOS/LazPaint.app/Contents/Info.plist

@@ -19,9 +19,9 @@
     <key>CFBundleSignature</key>
     <string>lazp</string>
     <key>CFBundleShortVersionString</key>
-    <string>7.1.1</string>
+    <string>7.1.2</string>
     <key>CFBundleVersion</key>
-    <string>7.1.1</string>
+    <string>7.1.2</string>
     <key>CSResourcesFileMapped</key>
     <true/>
     <key>CFBundleDocumentTypes</key>

+ 1 - 1
lazpaint/release/macOS/makedmg.sh

@@ -12,7 +12,7 @@ fi
 
 
 appname=LazPaint
-appversion=7.1.1
+appversion=7.1.2
 pkgversion=0
 appnamenospaces=lazpaint
 appbundle="$appname.app"

+ 69 - 2
lazpaint/release/windows/lazpaint.iss

@@ -1,13 +1,14 @@
 #define MyAppName "LazPaint"
 #define MyAppOutputName "lazpaint"
 #define MyInstallerSuffix "_setup_win32_win64"
-#define MyAppVersion "7.1.1"
+#define MyAppVersion "7.1.2"
 #define MyAppPublisher "Circular, Fabien Wang, Lainz and others"
 #define MyAppURL "http://sourceforge.net/projects/lazpaint/"
 #define MyAppExeName "lazpaint.exe"
 #define DCRawExeName "dcraw.exe"
 #define LibWebPDllName "libwebp.dll"
 #define ReleaseDir "..\bin\"
+#define ScriptsDir "..\..\..\scripts\"
 
 [Setup]
 AppId={{A177F82E-B44A-4348-A265-3D1C089D6304}
@@ -84,13 +85,20 @@ Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl"
 Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"
 Name: "assoc_lzp"; Description: "{cm:AssocFileExtension,{#MyAppName},.lzp}"
 Name: "assoc_ora"; Description: "{cm:AssocFileExtension,{#MyAppName},.ora}"
+Name: "assoc_oxo"; Description: "{cm:AssocFileExtension,{#MyAppName},.oxo}"; Flags: unchecked
 Name: "assoc_pdn"; Description: "{cm:AssocFileExtension,{#MyAppName},.pdn}"; Flags: unchecked
 Name: "assoc_bmp"; Description: "{cm:AssocFileExtension,{#MyAppName},.bmp}"; Flags: unchecked
 Name: "assoc_pcx"; Description: "{cm:AssocFileExtension,{#MyAppName},.pcx}"; Flags: unchecked
 Name: "assoc_png"; Description: "{cm:AssocFileExtension,{#MyAppName},.png}"; Flags: unchecked
-Name: "assoc_jpg"; Description: "{cm:AssocFileExtension,{#MyAppName},.jpg}"; Flags: unchecked
+Name: "assoc_jpg"; Description: "{cm:AssocFileExtension,{#MyAppName},.jpg .jpeg}"; Flags: unchecked
 Name: "assoc_gif"; Description: "{cm:AssocFileExtension,{#MyAppName},.gif}"; Flags: unchecked
 Name: "assoc_ico"; Description: "{cm:AssocFileExtension,{#MyAppName},.ico}"; Flags: unchecked
+Name: "assoc_cur"; Description: "{cm:AssocFileExtension,{#MyAppName},.cur}"; Flags: unchecked
+Name: "assoc_tga"; Description: "{cm:AssocFileExtension,{#MyAppName},.tga}"; Flags: unchecked
+Name: "assoc_tiff"; Description: "{cm:AssocFileExtension,{#MyAppName},.tif .tiff}"; Flags: unchecked
+Name: "assoc_webp"; Description: "{cm:AssocFileExtension,{#MyAppName},.webp}"; Flags: unchecked
+Name: "assoc_xpm"; Description: "{cm:AssocFileExtension,{#MyAppName},.xpm}"; Flags: unchecked
+
 
 [Files]
 Source: "{#ReleaseDir}lazpaint32.exe"; DestDir: "{app}"; DestName: "{#MyAppExeName}"; Flags: ignoreversion; Check: not Is64BitInstallMode
@@ -102,6 +110,9 @@ Source: "libwebp\libwebp64.dll"; DestDir: "{app}"; DestName: "{#LibWebPDllName}"
 Source: "{#ReleaseDir}i18n\*.po"; DestDir: "{app}\i18n"; Excludes: "i18n\lazpaint_x64.po"; Flags: ignoreversion
 Source: "{#ReleaseDir}models\*.*"; DestDir: "{app}\models"; Flags: ignoreversion
 Source: "{#ReleaseDir}readme.txt"; DestDir: "{app}"; Flags: ignoreversion
+Source: "{#ScriptsDir}*"; DestDir: "{app}\scripts"; Flags: ignoreversion; Excludes: "\__pycache__\*";  
+Source: "{#ScriptsDir}\lazpaint\*"; DestDir: "{app}\scripts\lazpaint"; Flags: ignoreversion; Excludes: "\__pycache__\*";  
+Source: "{#ScriptsDir}\test\*"; DestDir: "{app}\scripts\test"; Flags: ignoreversion; Excludes: "\__pycache__\*";  
 
 [Icons]
 Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Comment: "LazPaint"
@@ -123,6 +134,11 @@ Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.ora\DefaultIcon"; Value
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.ora\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.ora\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
 
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.oxo"; ValueType: String; ValueData: "PhoXo"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.oxo\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.oxo\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.oxo\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
+
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.bmp"; ValueType: String; ValueData: "Bitmap"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.bmp\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.bmp\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
@@ -138,6 +154,11 @@ Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.jpg\DefaultIcon"; Value
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.jpg\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.jpg\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
 
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.jpeg"; ValueType: String; ValueData: "JPEG"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.jpeg\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.jpeg\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.jpeg\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
+
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.pdn"; ValueType: String; ValueData: "Paint.NET image"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.pdn\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.pdn\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
@@ -153,32 +174,78 @@ Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.ico\DefaultIcon"; Value
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.ico\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.ico\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
 
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.cur"; ValueType: String; ValueData: "Cursor"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.cur\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.cur\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.cur\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
+
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.pcx"; ValueType: String; ValueData: "Personal Computer eXchange"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.pcx\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.pcx\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.pcx\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
 
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tga"; ValueType: String; ValueData: "Targa"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tga\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tga\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tga\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
+
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tif"; ValueType: String; ValueData: "Tiff"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tif\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tif\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tif\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
+
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tiff"; ValueType: String; ValueData: "Tiff"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tiff\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tiff\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.tiff\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
+
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.webp"; ValueType: String; ValueData: "WebP"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.webp\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.webp\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.webp\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
+
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.xpm"; ValueType: String; ValueData: "X PixMap"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.xpm\DefaultIcon"; ValueType: String; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.xpm\Shell\Open"; ValueName: Icon; ValueType: String; ValueData: "{app}\{#MyAppExeName}"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\Classes\LazPaint.AssocFile.xpm\Shell\Open\Command"; ValueType: String; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
+
 Root: HKLM; Subkey: "Software\Classes\.bmp"; ValueType: String; ValueData: "LazPaint.AssocFile.bmp"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_bmp
 Root: HKLM; Subkey: "Software\Classes\.lzp"; ValueType: String; ValueData: "LazPaint.AssocFile.lzp"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_lzp
 Root: HKLM; Subkey: "Software\Classes\.ora"; ValueType: String; ValueData: "LazPaint.AssocFile.ora"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_ora
+Root: HKLM; Subkey: "Software\Classes\.oxo"; ValueType: String; ValueData: "LazPaint.AssocFile.oxo"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_oxo
 Root: HKLM; Subkey: "Software\Classes\.png"; ValueType: String; ValueData: "LazPaint.AssocFile.png"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_png
 Root: HKLM; Subkey: "Software\Classes\.jpg"; ValueType: String; ValueData: "LazPaint.AssocFile.jpg"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_jpg
+Root: HKLM; Subkey: "Software\Classes\.jpg"; ValueType: String; ValueData: "LazPaint.AssocFile.jpeg"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_jpg
 Root: HKLM; Subkey: "Software\Classes\.pdn"; ValueType: String; ValueData: "LazPaint.AssocFile.pdn"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_pdn
 Root: HKLM; Subkey: "Software\Classes\.gif"; ValueType: String; ValueData: "LazPaint.AssocFile.gif"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_gif
 Root: HKLM; Subkey: "Software\Classes\.ico"; ValueType: String; ValueData: "LazPaint.AssocFile.ico"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_ico
 Root: HKLM; Subkey: "Software\Classes\.pcx"; ValueType: String; ValueData: "LazPaint.AssocFile.pcx"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_pcx
+Root: HKLM; Subkey: "Software\Classes\.tif"; ValueType: String; ValueData: "LazPaint.AssocFile.tiff"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_tiff
+Root: HKLM; Subkey: "Software\Classes\.tiff"; ValueType: String; ValueData: "LazPaint.AssocFile.tiff"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_tiff
+Root: HKLM; Subkey: "Software\Classes\.xpm"; ValueType: String; ValueData: "LazPaint.AssocFile.xpm"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_xpm
+Root: HKLM; Subkey: "Software\Classes\.webp"; ValueType: String; ValueData: "LazPaint.AssocFile.webp"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_webp
+Root: HKLM; Subkey: "Software\Classes\.tga"; ValueType: String; ValueData: "LazPaint.AssocFile.tga"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_tga
+Root: HKLM; Subkey: "Software\Classes\.cur"; ValueType: String; ValueData: "LazPaint.AssocFile.cur"; Flags: uninsdeletevalue uninsdeletekeyifempty; Tasks: assoc_cur
 
 Root: HKLM; Subkey: "Software\LazPaint"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities"; ValueType: String; ValueName: "ApplicationName"; ValueData: "LazPaint"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities"; ValueType: String; ValueName: "ApplicationDescription"; ValueData: "This program is designed to draw like with Paint.Net and to experiment this kind of programming with Lazarus."; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".lzp"; ValueType: String; ValueData: "LazPaint.AssocFile.lzp"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".ora"; ValueType: String; ValueData: "LazPaint.AssocFile.ora"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".oxo"; ValueType: String; ValueData: "LazPaint.AssocFile.oxo"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".bmp"; ValueType: String; ValueData: "LazPaint.AssocFile.bmp"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".png"; ValueType: String; ValueData: "LazPaint.AssocFile.png"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".jpg"; ValueType: String; ValueData: "LazPaint.AssocFile.jpg"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".jpeg"; ValueType: String; ValueData: "LazPaint.AssocFile.jpeg"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".pdn"; ValueType: String; ValueData: "LazPaint.AssocFile.pdn"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".gif"; ValueType: String; ValueData: "LazPaint.AssocFile.gif"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".ico"; ValueType: String; ValueData: "LazPaint.AssocFile.ico"; Flags: uninsdeletekey
 Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".pcx"; ValueType: String; ValueData: "LazPaint.AssocFile.pcx"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".tif"; ValueType: String; ValueData: "LazPaint.AssocFile.tiff"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".tiff"; ValueType: String; ValueData: "LazPaint.AssocFile.tiff"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".xpm"; ValueType: String; ValueData: "LazPaint.AssocFile.xpm"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".webp"; ValueType: String; ValueData: "LazPaint.AssocFile.webp"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".tga"; ValueType: String; ValueData: "LazPaint.AssocFile.tga"; Flags: uninsdeletekey
+Root: HKLM; Subkey: "Software\LazPaint\Capabilities\FileAssociations"; ValueName: ".cur"; ValueType: String; ValueData: "LazPaint.AssocFile.cur"; Flags: uninsdeletekey
 
 Root: HKLM; Subkey: "Software\RegisteredApplications"; ValueType: String; ValueName: "LazPaint"; ValueData: "Software\LazPaint\Capabilities"; Flags: uninsdeletevalue uninsdeletekeyifempty

+ 2 - 1
lazpaint/tablet/laztabletwin.pas

@@ -81,6 +81,7 @@ var
   AContext: TLogContext;
 begin
   inherited Create(AOwner);
+  FTablet := nil;
   FTablet := TTablet.Create(Self);
 
   FTablet.OnPacket := @TabletPacket;
@@ -118,7 +119,7 @@ end;
 
 destructor TCustomLazTablet.Destroy;
 begin
-  FTablet.Close;
+  if Assigned(FTablet) then FTablet.Close;
   inherited Destroy;
 end;
 

+ 28 - 16
lazpaint/tools/utoolvectorial.pas

@@ -926,7 +926,7 @@ function TEditShapeTool.Render(VirtualScreen: TBGRABitmap; VirtualScreenWidth,
   BitmapToVirtualScreen: TBitmapToVirtualScreenFunction): TRect;
 var
   orig, xAxis, yAxis: TPointF;
-  viewMatrix: TAffineMatrix;
+  viewMatrix, editMatrix: TAffineMatrix;
 begin
   if InvalidEditMode then StopEdit(false,false);
   with LayerOffset do
@@ -968,15 +968,21 @@ begin
         FRectEditor.PointSize := DoScaleX(PointSize,OriginalDPI);
       end;
       FRectEditor.Clear;
-      FRectEditor.Matrix := AffineMatrixTranslation(-0.5,-0.5)*viewMatrix*AffineMatrixTranslation(0.5,0.5);
-      if Assigned(FOriginalRect) then FOriginalRect.ConfigureEditor(FRectEditor);
-      if Assigned(FSelectionRect) then FSelectionRect.ConfigureEditor(FRectEditor);
-      if Assigned(VirtualScreen) then
-        result := FRectEditor.Render(VirtualScreen,
-          rect(0,0,VirtualScreenWidth,VirtualScreenHeight))
+      editMatrix := AffineMatrixTranslation(-0.5,-0.5)*viewMatrix*AffineMatrixTranslation(0.5,0.5);
+      if IsAffineMatrixInversible(editMatrix) then
+      begin
+        FRectEditor.Matrix := editMatrix;
+        if Assigned(FOriginalRect) then FOriginalRect.ConfigureEditor(FRectEditor);
+        if Assigned(FSelectionRect) then FSelectionRect.ConfigureEditor(FRectEditor);
+        if Assigned(VirtualScreen) then
+          result := FRectEditor.Render(VirtualScreen,
+            rect(0,0,VirtualScreenWidth,VirtualScreenHeight))
+        else
+          result := FRectEditor.GetRenderBounds(
+            rect(0,0,VirtualScreenWidth,VirtualScreenHeight));
+      end
       else
-        result := FRectEditor.GetRenderBounds(
-          rect(0,0,VirtualScreenWidth,VirtualScreenHeight));
+        result := EmptyRect;
     end;
   else
     begin
@@ -2320,6 +2326,7 @@ function TVectorialTool.Render(VirtualScreen: TBGRABitmap; VirtualScreenWidth,
   BitmapToVirtualScreen: TBitmapToVirtualScreenFunction): TRect;
 var
   orig, xAxis, yAxis: TPointF;
+  editMatrix: TAffineMatrix;
 begin
   if Assigned(FShape) then
   begin
@@ -2329,14 +2336,19 @@ begin
       xAxis := BitmapToVirtualScreen(PointF(1,0));
       yAxis := BitmapToVirtualScreen(PointF(0,1));
     end;
-    Editor.Matrix := AffineMatrix(xAxis-orig,yAxis-orig,orig)*VectorTransform(true);
     Editor.Clear;
-    Editor.PointSize := DoScaleX(PointSize, OriginalDPI);
-    if Assigned(FShape) then FShape.ConfigureEditor(Editor);
-    if Assigned(VirtualScreen) then
-      Result:= Editor.Render(VirtualScreen, rect(0,0,VirtualScreen.Width,VirtualScreen.Height))
-    else
-      Result:= Editor.GetRenderBounds(rect(0,0,VirtualScreenWidth,VirtualScreenHeight));
+    editMatrix := AffineMatrix(xAxis-orig,yAxis-orig,orig)*VectorTransform(true);
+    if IsAffineMatrixInversible(editMatrix) then
+    begin
+      Editor.Matrix := editMatrix;
+      Editor.PointSize := DoScaleX(PointSize, OriginalDPI);
+      if Assigned(FShape) then FShape.ConfigureEditor(Editor);
+      if Assigned(VirtualScreen) then
+        Result:= Editor.Render(VirtualScreen, rect(0,0,VirtualScreen.Width,VirtualScreen.Height))
+      else
+        Result:= Editor.GetRenderBounds(rect(0,0,VirtualScreenWidth,VirtualScreenHeight));
+    end else
+      result := EmptyRect;
   end else
   begin
     result := EmptyRect;

+ 17 - 42
lazpaint/ufileextensions.pas

@@ -181,53 +181,28 @@ begin
   if AUppercase then Result:=UTF8UpperCase(AStrUtf8) else Result:= UTF8LowerCase(AStrUtf8);
 end;
 
-{(en) Generates all possible upper and lowercase combinations of a string}
+{(en) Generates various cases that may be encountered}
 function SingleExtAllCases (ASingleExtension: string; Delimiter: String=';'; Prefix: string=''; Suffix: String=''):string;
 var
-  FWord,FChar:integer;
-  Len: integer;
-  Count: integer;
-  OrigArray,LCArray,UCArray: array of String;
-  CExt:string='';
-  Cased: Boolean;
+  otherCase: String;
 begin
-  Result := '';
-  Len:= Length(ASingleExtension);
-  Len:=UTF8Length(ASingleExtension);
-  Count:=(1 shl len) - 1;
-  SetLength(OrigArray,Len);
-  SetLength(LCArray,Len);
-  SetLength(UCArray,Len);
-  for FChar:=0 to Len -1 do
-  begin
-     OrigArray[FChar]:=Utf8Copy (ASingleExtension,FChar+1,1);
-     LCArray[FChar]:=UTF8LowerCase(OrigArray[FChar]);
-     UCArray[FChar]:=UTF8UpperCase(OrigArray[FChar]);
-  end;
-  for FWord:=0 to Count do
-  begin
-    CExt:='';
-    Cased:=True;
-    for FChar:=0 to Len-1 do
-    begin
-      if not GetBit(FWord,FChar) or (LCArray[FChar]<>UCArray[FChar]) then
-      begin
-        if (OrigArray[FChar] = UCArray[FChar]) xor GetBit(FWord,FChar) then
-          CExt += UCArray[FChar]
-        else
-          CExt += LCArray[FChar];
-      end
-      else begin Cased:= False; Break; end;
-    end; //for FChar
-    if Cased then
-    begin
-      if length(Result) > 0 then result += Delimiter;
-      Result:= Result+ Prefix+ CExt + Suffix;
-    end;  //if
-  end; //for FWord
+  Result := Prefix + ASingleExtension + Suffix;
+
+  otherCase := UTF8LowerCase(ASingleExtension);
+  if otherCase <> ASingleExtension then
+    Result += Delimiter + Prefix + otherCase + Suffix;
+
+  otherCase := UTF8UpperCase(ASingleExtension);
+  if otherCase <> ASingleExtension then
+    Result += Delimiter + Prefix + otherCase + Suffix;
+
+  otherCase := UTF8UpperCase(UTF8Copy(ASingleExtension, 1, 1)) +
+               UTF8LowerCase(UTF8Copy(ASingleExtension, 2, UTF8Length(ASingleExtension) - 1));
+  if otherCase <> ASingleExtension then
+    Result += Delimiter + Prefix + otherCase + Suffix;
 end;
 
-{(en) Generates all possible upper and lowercase combinations of file extensions}
+{(en) Generates various cases of file extensions}
 function ExtensionsAllCases (AllExtensions: String; ADelimiter: string = ';'; APrefix:string = '*.'): String;
 var
   ExtList: TStringList;

+ 170 - 0
lazpaint/uiconcache.pas

@@ -0,0 +1,170 @@
+unit UIconCache;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, BGRABitmap;
+
+procedure AddToCache(AFilenames: array of string; ALastModifications: array of TDateTime;
+          AIconSize: integer);
+function GetCachedIcon(AFilename: string; ALastModification: TDateTime; AInvalidIcon: TBGRABitmap): TBGRABitmap;
+procedure StopCaching(AWait: boolean = false);
+function IsCacheBusy: boolean;
+
+implementation
+
+uses URaw, BGRAThumbnail, UFileSystem, BGRAReadLzp, BGRABitmapTypes, BGRAWriteLzp;
+
+const
+  MaxIconCacheCount = 512;
+
+type
+
+  { TIconCacheThread }
+
+  TIconCacheThread = class(TThread)
+  private
+    FFilenames: array of string;
+    FLastModifications: array of TDateTime;
+    FIconSize: integer;
+  public
+    constructor Create(AFilenames: array of string;
+      ALastModifications: array of TDateTime; AIconSize: integer);
+    procedure Execute; override;
+  end;
+
+var
+  IconCache: TStringList;
+  IconCacheInvalid: TStringList;
+  CacheThread: TIconCacheThread;
+
+procedure AddToCache(AFilenames: array of string;
+  ALastModifications: array of TDateTime; AIconSize: integer);
+begin
+  if IsCacheBusy then
+    raise exception.Create('Cache is busy');
+  FreeAndNil(CacheThread);
+  CacheThread := TIconCacheThread.Create(AFilenames, ALastModifications, AIconSize);
+end;
+
+function GetCachedIcon(AFilename: string; ALastModification: TDateTime; AInvalidIcon: TBGRABitmap): TBGRABitmap;
+var
+  cacheName, dummyCaption: String;
+  cacheIndex: Integer;
+begin
+  if IsCacheBusy then exit(nil);
+  cacheName := AFilename+':'+FloatToStr(ALastModification);
+  cacheIndex := IconCache.IndexOf(cacheName);
+  if cacheIndex <> -1 then
+  begin
+    TStream(IconCache.Objects[cacheIndex]).Position:= 0;
+    result := TBGRABitmap.Create;
+    TBGRAReaderLazPaint.LoadRLEImage(TStream(IconCache.Objects[cacheIndex]), result, dummyCaption);
+    exit;
+  end else
+  if IconCacheInvalid.IndexOf(cacheName) <> -1 then
+    exit(AInvalidIcon)
+  else
+    exit(nil);
+end;
+
+procedure StopCaching(AWait: boolean);
+begin
+  if Assigned(CacheThread) then
+  begin
+    CacheThread.Terminate;
+    if AWait then CacheThread.WaitFor;
+  end;
+end;
+
+function IsCacheBusy: boolean;
+begin
+  result := Assigned(CacheThread) and not CacheThread.Finished;
+end;
+
+{ TIconCacheThread }
+
+constructor TIconCacheThread.Create(AFilenames: array of string;
+  ALastModifications: array of TDateTime; AIconSize: integer);
+var
+  i: Integer;
+begin
+  if length(AFilenames)<>length(ALastModifications) then
+    raise exception.Create('Array size mismatch');
+  setlength(FFilenames, length(AFilenames));
+  setlength(FLastModifications, length(FFilenames));
+  for i := 0 to high(FFilenames) do
+  begin
+    FFilenames[i] := AFilenames[i];
+    FLastModifications[i] := ALastModifications[i];
+  end;
+  FIconSize := AIconSize;
+
+  inherited Create(False);
+end;
+
+procedure TIconCacheThread.Execute;
+var
+  i, cacheIndex: Integer;
+  cacheName: String;
+  bmpIcon: TBGRABitmap;
+  found: Boolean;
+  s: TStream;
+  mem: TMemoryStream;
+  endTime: TDateTime;
+begin
+  bmpIcon := TBGRABitmap.Create;
+  endTime := Now + 150/MSecsPerDay;
+  for i := 0 to high(FFilenames) do
+  begin
+    if Terminated or (Now > endTime) then break;
+    cacheName := FFilenames[i] + ':' + FloatToStr(FLastModifications[i]);
+    cacheIndex := IconCache.IndexOf(cacheName);
+    if cacheIndex <> -1 then Continue;
+    try
+      s := FileManager.CreateFileStream(FFilenames[i], fmOpenRead or fmShareDenyWrite);
+      try
+        if IsRawFilename(FFilenames[i]) then
+        begin
+          found := GetRawStreamThumbnail(s, FIconSize, FIconSize, BGRAPixelTransparent,
+                                         True, bmpIcon) <> nil;
+        end else
+          found := GetStreamThumbnail(s, FIconSize, FIconSize, BGRAPixelTransparent,
+                                         True, ExtractFileExt(FFilenames[i]), bmpIcon) <> nil;
+      finally
+        s.Free;
+      end;
+    except
+      found := false;
+    end;
+    if found then
+    begin
+      if IconCache.Count >= MaxIconCacheCount then IconCache.Delete(0);
+      mem := TMemoryStream.Create;
+      TBGRAWriterLazPaint.WriteRLEImage(mem, bmpIcon);
+      IconCache.AddObject(cacheName, mem); //mem owned by IconCache
+    end else
+      IconCacheInvalid.Add(cacheName);
+  end;
+  bmpIcon.Free;
+end;
+
+
+initialization
+
+  IconCache := TStringList.Create;
+  IconCache.CaseSensitive := true;
+  IconCache.OwnsObjects := true;
+  IconCacheInvalid := TStringList.Create;
+  IconCacheInvalid.CaseSensitive := true;
+
+finalization
+
+  StopCaching(true);
+  CacheThread.Free;
+  IconCacheInvalid.Free;
+  IconCache.Free;
+
+end.

+ 18 - 4
lazpaint/uraw.pas

@@ -58,6 +58,9 @@ implementation
 
 uses process, BGRAThumbnail, UResourceStrings, UFileSystem, Forms, LazFileUtils;
 
+var
+  RawCriticalSection: TRTLCriticalSection;
+
 function GetAllRawExtensions: string;
 var
   i: Integer;
@@ -83,12 +86,18 @@ begin
   tempName := '';
   p := nil;
   try
-    tempName := GetTempFileName;
-    s := TFileStream.Create(tempName, fmCreate);
+
+    EnterCriticalsection(RawCriticalSection);
     try
-      s.CopyFrom(AInputStream, AInputStream.Size);
+      tempName := GetTempFileName;
+      s := TFileStream.Create(tempName, fmCreate);
+      try
+        s.CopyFrom(AInputStream, AInputStream.Size);
+      finally
+        s.Free;
+      end;
     finally
-      s.Free;
+      LeaveCriticalsection(RawCriticalSection);
     end;
 
     p := TProcess.Create(nil);
@@ -218,6 +227,11 @@ end;
 initialization
 
   AllRawExtensions := GetAllRawExtensions;
+  InitCriticalSection(RawCriticalSection);
+
+finalization
+
+  DoneCriticalsection(RawCriticalSection);
 
 end.
 

+ 5 - 3
lazpaintcontrols/lcvectororiginal.pas

@@ -2427,6 +2427,7 @@ var
   sb: TRectF;
   m: TAffineMatrix;
 begin
+  if not IsAffineMatrixInversible(AMatrix) then exit;
   sb := GetAlignBounds(ABounds, AMatrix);
   case AAlign of
   taRightJustify: m := AffineMatrixTranslation(ABounds.Right-sb.Right,0);
@@ -2442,6 +2443,7 @@ var
   sb: TRectF;
   m: TAffineMatrix;
 begin
+  if not IsAffineMatrixInversible(AMatrix) then exit;
   sb := GetAlignBounds(ABounds, AMatrix);
   case AAlign of
   tlBottom: m := AffineMatrixTranslation(0,ABounds.Bottom-sb.Bottom);
@@ -3234,9 +3236,9 @@ begin
       allRectF := rectF(0,0,ADest.Width,ADest.Height);
       FUnfrozenRangeStart := newUnfrozenRangeStart;
       FUnfrozenRangeEnd := newUnfrozenRangeEnd;
+      FreeAndNil(FFrozenShapesUnderSelection);
       if FUnfrozenRangeStart > 0 then
       begin
-        FreeAndNil(FFrozenShapesUnderSelection);
         FFrozenShapesUnderBounds := GetRenderBounds(rect(0,0,ADest.Width,ADest.Height), mOfs,
                                       0, FUnfrozenRangeStart-1);
         FFrozenShapesUnderBounds.Intersect(rect(0,0,ADest.Width,ADest.Height));
@@ -3261,10 +3263,10 @@ begin
       for i := FUnfrozenRangeStart to FUnfrozenRangeEnd-1 do
         if FShapes[i].GetRenderBounds(ADest.ClipRect, mOfs, []).IntersectsWith(clipRectF) then
           FShapes[i].Render(ADest, ARenderOffset, AMatrix, ADraft);
+      FreeAndNil(FFrozenShapesOverSelection);
       if FUnfrozenRangeEnd < FShapes.Count then
       begin
-        FreeAndNil(FFrozenShapesOverSelection);
-        FFrozenShapesOverBounds := GetRenderBounds(rect(0,0,ADest.Width,ADest.Height), AMatrix,
+        FFrozenShapesOverBounds := GetRenderBounds(rect(0,0,ADest.Width,ADest.Height), mOfs,
                                      FUnfrozenRangeEnd, FShapes.Count-1);
         FFrozenShapesOverBounds.Intersect(rect(0,0,ADest.Width,ADest.Height));
         FFrozenShapesOverSelection := TBGRABitmap.Create(FFrozenShapesOverBounds.Width, FFrozenShapesOverBounds.Height);

+ 1 - 0
lazpaintcontrols/lcvectorpolyshapes.pas

@@ -1102,6 +1102,7 @@ var
   i, nbTotal: Integer;
 begin
   FViewMatrix := AEditor.Matrix;
+  if not IsAffineMatrixInversible(FViewMatrix) then exit;
   FViewMatrixInverse := AffineMatrixInverse(FViewMatrix);
   FGridMatrix := AEditor.GridMatrix;
 

+ 9 - 5
lazpaintcontrols/lcvectorrectshapes.pas

@@ -243,10 +243,12 @@ procedure TPhongShapeDiff.Unapply(AEndShape: TVectorShape);
 begin
   with (AEndShape as TPhongShape) do
   begin
+    BeginUpdate;
     FShapeKind := FStartShapeKind;
     FLightPosition := FStartLightPosition;
     FShapeAltitudePercent := FStartShapeAltitudePercent;
     FBorderSizePercent := FStartBorderSizePercent;
+    EndUpdate;
   end;
 end;
 
@@ -1624,7 +1626,7 @@ var
   mapWidth,mapHeight: integer;
   shader: TPhongShading;
   approxFactor,borderSize: single;
-  m: TAffineMatrix;
+  m,mInv: TAffineMatrix;
   h, lightPosZ: single;
   map,raster: TBGRABitmap;
   u,v,lightPosF: TPointF;
@@ -1644,9 +1646,9 @@ begin
 
   //determine map size before transform
   ab := GetAffineBox(AMatrix, false);
+  if (ab.Width = 0) or (ab.Height = 0) then exit;
   if ab.Width > ab.Height then
   begin
-    if ab.Width = 0 then exit;
     mapWidth := ceil(ab.Width);
     mapHeight := ceil(ab.Surface/ab.Width);
   end else
@@ -1672,6 +1674,8 @@ begin
   v := (ab.BottomLeft-ab.TopLeft)*(1/ab.Height);
   m := AffineMatrix(u,v,ab.TopLeft)*AffineMatrixScale(ab.Width/mapWidth,ab.Height/mapHeight);
   borderSize := FBorderSizePercent/200*min(ab.Width,ab.Height);
+  if not IsAffineMatrixInversible(m) then exit;
+  mInv := AffineMatrixInverse(m);
 
   try
     //create height map
@@ -1713,7 +1717,7 @@ begin
       end;
     end;
 
-    abRaster := AffineMatrixInverse(m)*TAffineBox.AffineBox(rectRenderF);
+    abRaster := mInv*TAffineBox.AffineBox(rectRenderF);
     rectRasterF := abRaster.RectBoundsF;
     rectRaster := rect(floor(rectRasterF.Left),floor(rectRasterF.Top),ceil(rectRasterF.Right),ceil(rectRasterF.Bottom));
 
@@ -1725,7 +1729,7 @@ begin
       shader.AmbientFactor := 0.5;
       shader.NegativeDiffusionFactor := 0.15;
       lightPosF := AffineMatrixTranslation(-rectRaster.Left,-rectRaster.Top)
-                    *AffineMatrixInverse(m)*AMatrix*FLightPosition;
+                    *mInv*AMatrix*FLightPosition;
       lightPosZ := 100*Power(approxFactor,1.1);
       if h*3/2 > lightPosZ then lightposZ := h*3/2;
       shader.LightPosition3D := Point3D(lightPosF.x,lightPosF.y,lightPosZ);
@@ -1735,7 +1739,7 @@ begin
         shader.Draw(raster,map,h,-rectRaster.Left,-rectRaster.Top,BackFill.SolidColor)
       else
       begin
-        scan := BackFill.CreateScanner(AffineMatrixTranslation(-rectRaster.left,-rectRaster.top)*AffineMatrixInverse(m)*AMatrix,ADraft);
+        scan := BackFill.CreateScanner(AffineMatrixTranslation(-rectRaster.left,-rectRaster.top)*mInv*AMatrix,ADraft);
         shader.DrawScan(raster,map,h,-rectRaster.Left,-rectRaster.Top,scan);
         scan.Free;
       end;

+ 8 - 2
lazpaintcontrols/lcvectortextshapes.pas

@@ -931,10 +931,13 @@ var
   newPos: Integer;
   tl: TBidiTextLayout;
   zoom: Single;
+  untransformed: TAffineMatrix;
 begin
   tl := GetTextLayout;
   zoom := GetTextRenderZoom;
-  newPos := tl.GetCharIndexAt(AffineMatrixScale(zoom,zoom)*AffineMatrixInverse(GetUntransformedMatrix)*PointF(X,Y));
+  untransformed := GetUntransformedMatrix;
+  if not IsAffineMatrixInversible(untransformed) then exit;
+  newPos := tl.GetCharIndexAt(AffineMatrixScale(zoom,zoom)*AffineMatrixInverse(untransformed)*PointF(X,Y));
   if newPos<>-1 then
   begin
     if (newPos <> FSelEnd) or (not AExtend and (FSelStart <> FSelEnd)) or (UserMode <> vsuEditText) then
@@ -1497,12 +1500,15 @@ var
   tl: TBidiTextLayout;
   pt: TPointF;
   i: Integer;
+  untransformed: TAffineMatrix;
 begin
   if not GetAffineBox(AffineMatrixIdentity,true).Contains(APoint) then
     exit(false);
   SetGlobalMatrix(AffineMatrixIdentity);
   tl := GetTextLayout;
-  pt := AffineMatrixInverse(GetUntransformedMatrix)*APoint;
+  untransformed := GetUntransformedMatrix;
+  if not IsAffineMatrixInversible(untransformed) then exit;
+  pt := AffineMatrixInverse(untransformed)*APoint;
   for i := 0 to tl.PartCount-1 do
     if tl.PartAffineBox[i].Contains(pt) then exit(true);
   result := false;