From b491cc8e5aa731a9d7b4c0d425e778664b72336e Mon Sep 17 00:00:00 2001 From: John Lewin Date: Thu, 21 Jun 2018 09:02:31 -0700 Subject: [PATCH 1/6] Add type for thumbnail cache, move to ApplicationController --- ApplicationView/ApplicationController.cs | 13 +--- ApplicationView/PrinterModels.cs | 2 +- .../ContentProviders/MeshContentProvider.cs | 69 ++----------------- Library/Widgets/ListView/ListViewItemBase.cs | 2 +- MatterControl.csproj | 1 + PartPreviewWindow/Object3DTreeBuilder.cs | 6 +- 6 files changed, 12 insertions(+), 81 deletions(-) diff --git a/ApplicationView/ApplicationController.cs b/ApplicationView/ApplicationController.cs index 3d499c027..460a78540 100644 --- a/ApplicationView/ApplicationController.cs +++ b/ApplicationView/ApplicationController.cs @@ -361,6 +361,8 @@ namespace MatterHackers.MatterControl public Dictionary OperationsByType { get; private set; } + public ThumbnailCache Thumbnails { get; } = new ThumbnailCache(); + private void RebuildSceneOperations(ThemeConfig theme) { registeredSceneOperations = new List() @@ -1344,17 +1346,6 @@ namespace MatterHackers.MatterControl public string ShortProductName => "MatterControl"; public string ProductName => "MatterHackers: MatterControl"; - public string ThumbnailCachePath(string cacheId) - { - // TODO: Use content SHA - return ApplicationController.CacheablePath("ItemThumbnails", $"{cacheId}.png"); - } - - public string ThumbnailCachePath(string id, int width, int height) - { - return ApplicationController.CacheablePath("ItemThumbnails", $"{id}-{width}x{height}.png"); - } - public void SwitchToPurchasedLibrary() { var purchasedContainer = Library.RootLibaryContainer.ChildContainers.Where(c => c.ID == "LibraryProviderPurchasedKey").FirstOrDefault(); diff --git a/ApplicationView/PrinterModels.cs b/ApplicationView/PrinterModels.cs index 35e7fd905..1266ca2a1 100644 --- a/ApplicationView/PrinterModels.cs +++ b/ApplicationView/PrinterModels.cs @@ -595,7 +595,7 @@ namespace MatterHackers.MatterControl { if (!this.FreezeGCode) { - var thumbnailPath = ApplicationController.Instance.ThumbnailCachePath(this.SourceItem.ID); + var thumbnailPath = ApplicationController.Instance.Thumbnails.ThumbnailCachePath(this.SourceItem.ID); if (File.Exists(thumbnailPath)) { File.Delete(thumbnailPath); diff --git a/Library/ContentProviders/MeshContentProvider.cs b/Library/ContentProviders/MeshContentProvider.cs index d2468dba4..e8e9ac1fa 100644 --- a/Library/ContentProviders/MeshContentProvider.cs +++ b/Library/ContentProviders/MeshContentProvider.cs @@ -122,38 +122,20 @@ namespace MatterHackers.MatterControl string thumbnailId = libraryItem.ID; - return GetThumbnail(object3D, thumbnailId, width, height, false); + return GetThumbnail(object3D, thumbnailId, width, height); } - public ImageBuffer GetThumbnail(IObject3D item, string thumbnailId, int width, int height, bool onlyUseCache) + public ImageBuffer GetThumbnail(IObject3D item, string thumbnailId, int width, int height) { if (item == null) { return DefaultImage; } - var image = LoadCachedImage(thumbnailId, width, height); - - if(image == null) - { - // check the mesh cache - image = LoadCachedImage(item.MeshRenderId().ToString(), width, height); - } - - if(image != null) - { - return image; - } - - if(onlyUseCache) - { - return DefaultImage; - } - int estimatedMemorySize = item.EstimatedMemory(); if (estimatedMemorySize > MaxFileSizeForThumbnail) { - return null; + return DefaultImage; } bool forceOrthographic = false; @@ -173,14 +155,9 @@ namespace MatterHackers.MatterControl if (thumbnail != null) { + // TODO: Consider and resolve who should own populating the cache // Cache at requested size - string cachePath = ApplicationController.Instance.ThumbnailCachePath(item.MeshRenderId().ToString(), width, height); - - // TODO: Lookup best large image and downscale if required - if (false) - { - thumbnail = LibraryProviderHelpers.ResizeImage(thumbnail, width, height); - } + string cachePath = ApplicationController.Instance.Thumbnails.ThumbnailCachePath(item.MeshRenderId().ToString(), width, height); AggContext.ImageIO.SaveImageData(cachePath, thumbnail); } @@ -188,42 +165,6 @@ namespace MatterHackers.MatterControl return thumbnail ?? DefaultImage; } - internal static ImageBuffer LoadCachedImage(string cacheId, int width, int height) - { - ImageBuffer cachedItem = LoadImage(ApplicationController.Instance.ThumbnailCachePath(cacheId, width, height)); - if (cachedItem != null) - { - return cachedItem; - } - - if (width < 100 - && height < 100) - { - // check for a 100x100 image - var cachedAt100x100 = LoadImage(ApplicationController.Instance.ThumbnailCachePath(cacheId, 100, 100)); - if (cachedAt100x100 != null) - { - return cachedAt100x100.CreateScaledImage(width, height); - } - } - - return null; - } - - private static ImageBuffer LoadImage(string filePath) - { - try - { - if (File.Exists(filePath)) - { - return AggContext.ImageIO.LoadImage(filePath).SetPreMultiply(); - } - } - catch { } // Suppress exceptions, return null on any errors - - return null; - } - public ImageBuffer DefaultImage => AggContext.StaticData.LoadIcon("mesh.png"); } } \ No newline at end of file diff --git a/Library/Widgets/ListView/ListViewItemBase.cs b/Library/Widgets/ListView/ListViewItemBase.cs index 9a9b70092..9ca99d75c 100644 --- a/Library/Widgets/ListView/ListViewItemBase.cs +++ b/Library/Widgets/ListView/ListViewItemBase.cs @@ -72,7 +72,7 @@ namespace MatterHackers.MatterControl.CustomWidgets string thumbnailId = libraryItem.ID; - var thumbnail = MeshContentProvider.LoadCachedImage(thumbnailId, thumbWidth, thumbHeight); + var thumbnail = ApplicationController.Instance.Thumbnails.LoadCachedImage(thumbnailId, thumbWidth, thumbHeight); if (thumbnail != null) { this.SetItemThumbnail(thumbnail); diff --git a/MatterControl.csproj b/MatterControl.csproj index 6a3b68958..88c6b654c 100644 --- a/MatterControl.csproj +++ b/MatterControl.csproj @@ -106,6 +106,7 @@ + diff --git a/PartPreviewWindow/Object3DTreeBuilder.cs b/PartPreviewWindow/Object3DTreeBuilder.cs index 8f0df10c6..f3a51db9e 100644 --- a/PartPreviewWindow/Object3DTreeBuilder.cs +++ b/PartPreviewWindow/Object3DTreeBuilder.cs @@ -107,12 +107,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow && ApplicationController.Instance.Library.ContentProviders.TryGetValue("mcx", out IContentProvider contentProvider) && contentProvider is MeshContentProvider meshContentProvider) { - node.Image = meshContentProvider.GetThumbnail( - item.Source, + node.Image = ApplicationController.Instance.Thumbnails.LoadCachedImage( item.Source.MeshRenderId().ToString(), 16, - 16, - true); + 16); } return Task.CompletedTask; From 262ef1ebb07b555234878991c7abfb6dd7a7096c Mon Sep 17 00:00:00 2001 From: John Lewin Date: Thu, 21 Jun 2018 09:11:46 -0700 Subject: [PATCH 2/6] Simplify naming --- ApplicationView/PrinterModels.cs | 2 +- Library/ContentProviders/MeshContentProvider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ApplicationView/PrinterModels.cs b/ApplicationView/PrinterModels.cs index 1266ca2a1..2850acca4 100644 --- a/ApplicationView/PrinterModels.cs +++ b/ApplicationView/PrinterModels.cs @@ -595,7 +595,7 @@ namespace MatterHackers.MatterControl { if (!this.FreezeGCode) { - var thumbnailPath = ApplicationController.Instance.Thumbnails.ThumbnailCachePath(this.SourceItem.ID); + var thumbnailPath = ApplicationController.Instance.Thumbnails.CachePath(this.SourceItem.ID); if (File.Exists(thumbnailPath)) { File.Delete(thumbnailPath); diff --git a/Library/ContentProviders/MeshContentProvider.cs b/Library/ContentProviders/MeshContentProvider.cs index e8e9ac1fa..3efcd398a 100644 --- a/Library/ContentProviders/MeshContentProvider.cs +++ b/Library/ContentProviders/MeshContentProvider.cs @@ -157,7 +157,7 @@ namespace MatterHackers.MatterControl { // TODO: Consider and resolve who should own populating the cache // Cache at requested size - string cachePath = ApplicationController.Instance.Thumbnails.ThumbnailCachePath(item.MeshRenderId().ToString(), width, height); + string cachePath = ApplicationController.Instance.Thumbnails.CachePath(item.MeshRenderId().ToString(), width, height); AggContext.ImageIO.SaveImageData(cachePath, thumbnail); } From 200a01edaf7ee4865db4382225a6a079bad36b28 Mon Sep 17 00:00:00 2001 From: John Lewin Date: Thu, 21 Jun 2018 09:56:52 -0700 Subject: [PATCH 3/6] Revise operation -> icon mapping system - MatterHackers/MCCentral#3703 SelectionGroup lacks custom tree icon --- ApplicationView/ApplicationController.cs | 18 ++++++++++++------ DesignTools/Operations/Group3D.cs | 5 +---- PartPreviewWindow/Object3DTreeBuilder.cs | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/ApplicationView/ApplicationController.cs b/ApplicationView/ApplicationController.cs index 460a78540..cc450b2c7 100644 --- a/ApplicationView/ApplicationController.cs +++ b/ApplicationView/ApplicationController.cs @@ -359,8 +359,6 @@ namespace MatterHackers.MatterControl private List registeredSceneOperations; - public Dictionary OperationsByType { get; private set; } - public ThumbnailCache Thumbnails { get; } = new ThumbnailCache(); private void RebuildSceneOperations(ThemeConfig theme) @@ -587,17 +585,25 @@ namespace MatterHackers.MatterControl }, }; - var operationsByType = new Dictionary(); + var operationIconsByType = new Dictionary(); - foreach(var operation in registeredSceneOperations) + foreach (var operation in registeredSceneOperations) { if (operation.OperationType != null) { - operationsByType.Add(operation.OperationType, operation); + operationIconsByType.Add(operation.OperationType, operation.Icon); } } - this.OperationsByType = operationsByType; + // TODO: Use custom selection group icon if reusing group icon seems incorrect + // + // Explicitly register SelectionGroup icon + if (operationIconsByType.TryGetValue(typeof(Group3D), out ImageBuffer groupIcon)) + { + operationIconsByType.Add(typeof(SelectionGroup), groupIcon); + } + + this.Thumbnails.OperationIcons = operationIconsByType; } public ImageSequence GetProcessingSequence(Color color) diff --git a/DesignTools/Operations/Group3D.cs b/DesignTools/Operations/Group3D.cs index 2e67bda52..f2928e1c8 100644 --- a/DesignTools/Operations/Group3D.cs +++ b/DesignTools/Operations/Group3D.cs @@ -27,11 +27,8 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ -using System.Linq; -using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; using MatterHackers.Localizations; -using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.DesignTools.Operations { @@ -41,7 +38,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations { Name = "Group".Localize(); } - + public override bool CanRemove => true; } } \ No newline at end of file diff --git a/PartPreviewWindow/Object3DTreeBuilder.cs b/PartPreviewWindow/Object3DTreeBuilder.cs index f3a51db9e..5fc625ea0 100644 --- a/PartPreviewWindow/Object3DTreeBuilder.cs +++ b/PartPreviewWindow/Object3DTreeBuilder.cs @@ -91,10 +91,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow }; // Check for operation resulting in the given type - if (ApplicationController.Instance.OperationsByType.TryGetValue(item.Source.GetType(), out SceneSelectionOperation operation)) + if (ApplicationController.Instance.Thumbnails.OperationIcons.TryGetValue(item.Source.GetType(), out ImageBuffer icon)) { // If exists, use the operation icon - node.Image = operation.Icon; + node.Image = icon; } else { From b98bb39136f5169565bc71f06314dc151817762b Mon Sep 17 00:00:00 2001 From: John Lewin Date: Thu, 21 Jun 2018 09:57:25 -0700 Subject: [PATCH 4/6] Remove raytracing pattern for TreeNodes, directly load icon from cache --- PartPreviewWindow/Object3DTreeBuilder.cs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/PartPreviewWindow/Object3DTreeBuilder.cs b/PartPreviewWindow/Object3DTreeBuilder.cs index 5fc625ea0..7e5fb20f4 100644 --- a/PartPreviewWindow/Object3DTreeBuilder.cs +++ b/PartPreviewWindow/Object3DTreeBuilder.cs @@ -31,6 +31,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MatterHackers.Agg.Image; using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; using MatterHackers.Localizations; @@ -100,21 +101,9 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { node.Load += (s, e) => { - ApplicationController.Instance.QueueForGeneration(() => - { - // When this widget is dequeued for generation, validate before processing. Off-screen widgets should be skipped and will requeue next time they become visible - if (node.ActuallyVisibleOnScreen() - && ApplicationController.Instance.Library.ContentProviders.TryGetValue("mcx", out IContentProvider contentProvider) - && contentProvider is MeshContentProvider meshContentProvider) - { - node.Image = ApplicationController.Instance.Thumbnails.LoadCachedImage( - item.Source.MeshRenderId().ToString(), - 16, - 16); - } - - return Task.CompletedTask; - }); + string contentID = item.Source.MeshRenderId().ToString(); + var thumbnail = ApplicationController.Instance.Thumbnails.LoadCachedImage(contentID, 16, 16); + node.Image = thumbnail ?? ApplicationController.Instance.Thumbnails.DefaultThumbnail; }; } From 6dea15a6d738a7299c3f1098e44cf3bcb24d3b87 Mon Sep 17 00:00:00 2001 From: John Lewin Date: Thu, 21 Jun 2018 10:50:40 -0700 Subject: [PATCH 5/6] Consolidate thumbnails behavior into new Thumbnails class --- ApplicationView/ApplicationController.cs | 72 +------------------- Library/Widgets/ListView/ListViewItemBase.cs | 2 +- 2 files changed, 3 insertions(+), 71 deletions(-) diff --git a/ApplicationView/ApplicationController.cs b/ApplicationView/ApplicationController.cs index cc450b2c7..1bb8c86fe 100644 --- a/ApplicationView/ApplicationController.cs +++ b/ApplicationView/ApplicationController.cs @@ -163,10 +163,6 @@ namespace MatterHackers.MatterControl public static Func>> GetProfileHistory; - private readonly static object thumbsLock = new object(); - - private Queue> queuedThumbCallbacks = new Queue>(); - public async Task SetActivePrinter(PrinterConfig printer, bool allowChangedEvent = true) { var initialPrinter = this.ActivePrinter; @@ -279,69 +275,6 @@ namespace MatterHackers.MatterControl }*/ } - private AutoResetEvent thumbGenResetEvent = new AutoResetEvent(false); - - Task thumbnailGenerator = null; - - internal void QueueForGeneration(Func func) - { - lock (thumbsLock) - { - if (thumbnailGenerator == null) - { - // Spin up a new thread once needed - thumbnailGenerator = Task.Run((Action)ThumbGeneration); - } - - queuedThumbCallbacks.Enqueue(func); - thumbGenResetEvent.Set(); - } - } - - private async void ThumbGeneration() - { - Thread.CurrentThread.Name = $"ThumbnailGeneration"; - - while (!this.ApplicationExiting) - { - Thread.Sleep(100); - - try - { - if (queuedThumbCallbacks.Count > 0) - { - Func callback; - lock (thumbsLock) - { - callback = queuedThumbCallbacks.Dequeue(); - } - - await callback(); - } - else - { - // Process until queuedThumbCallbacks is empty then wait for new tasks via QueueForGeneration - thumbGenResetEvent.WaitOne(); - } - } - catch (AppDomainUnloadedException) - { - return; - } - catch (ThreadAbortException) - { - return; - } - catch (Exception ex) - { - Console.WriteLine("Error generating thumbnail: " + ex.Message); - } - } - - // Null task reference on exit - thumbnailGenerator = null; - } - public static Func> GetPrinterProfileAsync; public static Func, Task> SyncPrinterProfiles; public static Func> GetPublicProfileList; @@ -1103,7 +1036,7 @@ namespace MatterHackers.MatterControl // Ensure all threads shutdown gracefully on close // Release any waiting generator threads - thumbGenResetEvent?.Set(); + this.Thumbnails.Shutdown(); // Kill all long running tasks (this will release the silcing thread if running) foreach (var task in Tasks.RunningTasks) @@ -1302,8 +1235,7 @@ namespace MatterHackers.MatterControl public async void OnApplicationClosed() { - // Release the waiting ThumbnailGeneration task so it can shutdown gracefully - thumbGenResetEvent?.Set(); + this.Thumbnails.Shutdown(); // Save changes before close if (this.ActivePrinter != null diff --git a/Library/Widgets/ListView/ListViewItemBase.cs b/Library/Widgets/ListView/ListViewItemBase.cs index 9ca99d75c..1a34b542a 100644 --- a/Library/Widgets/ListView/ListViewItemBase.cs +++ b/Library/Widgets/ListView/ListViewItemBase.cs @@ -135,7 +135,7 @@ namespace MatterHackers.MatterControl.CustomWidgets return; } - ApplicationController.Instance.QueueForGeneration(async () => + ApplicationController.Instance.Thumbnails.QueueForGeneration(async () => { // When dequeued for generation, ensure visible before raytracing. Off-screen widgets are dequeue and will reschedule if redrawn if (!this.ActuallyVisibleOnScreen()) From 3e0fa3c7ea17dff8a0f2b9b17018e0693f4cd490 Mon Sep 17 00:00:00 2001 From: John Lewin Date: Thu, 21 Jun 2018 10:52:56 -0700 Subject: [PATCH 6/6] Rename thumbnails type --- ApplicationView/ApplicationController.cs | 2 +- ApplicationView/ThumbnailsConfig.cs | 168 +++++++++++++++++++++++ MatterControl.csproj | 2 +- 3 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 ApplicationView/ThumbnailsConfig.cs diff --git a/ApplicationView/ApplicationController.cs b/ApplicationView/ApplicationController.cs index 1bb8c86fe..f1d9d22c1 100644 --- a/ApplicationView/ApplicationController.cs +++ b/ApplicationView/ApplicationController.cs @@ -292,7 +292,7 @@ namespace MatterHackers.MatterControl private List registeredSceneOperations; - public ThumbnailCache Thumbnails { get; } = new ThumbnailCache(); + public ThumbnailsConfig Thumbnails { get; } = new ThumbnailsConfig(); private void RebuildSceneOperations(ThemeConfig theme) { diff --git a/ApplicationView/ThumbnailsConfig.cs b/ApplicationView/ThumbnailsConfig.cs new file mode 100644 index 000000000..243790a0f --- /dev/null +++ b/ApplicationView/ThumbnailsConfig.cs @@ -0,0 +1,168 @@ +/* +Copyright (c) 2018, Lars Brubaker, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +namespace MatterHackers.MatterControl +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using MatterHackers.Agg.Image; + using MatterHackers.Agg.Platform; + using MatterHackers.MatterControl.Library; + + public class ThumbnailsConfig + { + private readonly static object thumbsLock = new object(); + + private Queue> queuedThumbCallbacks = new Queue>(); + + public Dictionary OperationIcons { get; internal set; } + + public ImageBuffer DefaultThumbnail { get; } = AggContext.StaticData.LoadIcon("cube.png"); + + public ImageBuffer LoadCachedImage(string cacheId, int width, int height) + { + ImageBuffer cachedItem = LoadImage(this.CachePath(cacheId, width, height)); + if (cachedItem != null) + { + return cachedItem; + } + + if (width < 100 + && height < 100) + { + // check for a 100x100 image + var cachedAt100x100 = LoadImage(this.CachePath(cacheId, 100, 100)); + if (cachedAt100x100 != null) + { + return cachedAt100x100.CreateScaledImage(width, height); + } + } + + return null; + } + + public string CachePath(string cacheId) + { + // TODO: Use content SHA + return ApplicationController.CacheablePath("ItemThumbnails", $"{cacheId}.png"); + } + + public string CachePath(string id, int width, int height) + { + return ApplicationController.CacheablePath("ItemThumbnails", $"{id}-{width}x{height}.png"); + } + + private AutoResetEvent thumbGenResetEvent = new AutoResetEvent(false); + + private Task thumbnailGenerator = null; + + internal void QueueForGeneration(Func func) + { + lock (thumbsLock) + { + if (thumbnailGenerator == null) + { + // Spin up a new thread once needed + thumbnailGenerator = Task.Run((Action)ThumbGeneration); + } + + queuedThumbCallbacks.Enqueue(func); + thumbGenResetEvent.Set(); + } + } + + private async void ThumbGeneration() + { + Thread.CurrentThread.Name = $"ThumbnailGeneration"; + + while (!ApplicationController.Instance.ApplicationExiting) + { + Thread.Sleep(100); + + try + { + if (queuedThumbCallbacks.Count > 0) + { + Func callback; + lock (thumbsLock) + { + callback = queuedThumbCallbacks.Dequeue(); + } + + await callback(); + } + else + { + // Process until queuedThumbCallbacks is empty then wait for new tasks via QueueForGeneration + thumbGenResetEvent.WaitOne(); + } + } + catch (AppDomainUnloadedException) + { + return; + } + catch (ThreadAbortException) + { + return; + } + catch (Exception ex) + { + Console.WriteLine("Error generating thumbnail: " + ex.Message); + } + } + + // Null task reference on exit + thumbnailGenerator = null; + } + + + private static ImageBuffer LoadImage(string filePath) + { + try + { + if (File.Exists(filePath)) + { + return AggContext.ImageIO.LoadImage(filePath).SetPreMultiply(); + } + } + catch { } // Suppress exceptions, return null on any errors + + return null; + } + + public void Shutdown() + { + // Release the waiting ThumbnailGeneration task so it can shutdown gracefully + thumbGenResetEvent?.Set(); + } + } +} \ No newline at end of file diff --git a/MatterControl.csproj b/MatterControl.csproj index 88c6b654c..4f8cfb9a0 100644 --- a/MatterControl.csproj +++ b/MatterControl.csproj @@ -75,6 +75,7 @@ + @@ -106,7 +107,6 @@ -