/* * GNU AFFERO GENERAL PUBLIC LICENSE * Version 3, 19 November 2007 * Copyright (C) 2007 Free Software Foundation, Inc. * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ using Emgu.CV; using Emgu.CV.CvEnum; using System; using System.Diagnostics; using System.Threading.Tasks; using UVtools.Core.FileFormats; using UVtools.Core.Operations; namespace UVtools.Core.Managers; public class MatCacheManager : IDisposable { /// /// Gets the slicer file /// public FileFormat SlicerFile { get; } /// /// Gets or sets the cache count of items to keep in memory /// public ushort CacheCount { get; set; } /// /// Gets the number of cached elements per a cache entry /// public byte ElementsPerCache { get; } /// /// Gets the starting layer index range /// public uint LayerIndexStart { get; } /// /// Gets the ending layer index range /// public uint LayerIndexEnd { get; } /// /// Gets the size of this collection /// public uint CollectionSize => LayerIndexEnd - LayerIndexStart + 1; /// /// Gets the image rotation to cache /// public RotateDirection Rotate { get; init; } = RotateDirection.None; /// /// Gets the image flip to cache /// public FlipDirection Flip { get; init; } = FlipDirection.None; /// /// Gets if striping anti-aliasing is enabled, cache will be threshold'ed /// public bool StripAntiAliasing { get; init; } /// /// Gets or sets the cache direction, false to go backwards, true to go forward /// public bool Direction { get; set; } = true; /// /// If enabled it will not calculate the cache index given a layer index, set this to true to use your own indexing without pair them with layers /// public bool UseRawIndexInsteadOfLayerIndex { get; init; } /// /// Gets or sets the auto dispose mode, it will dispose all mat's below of above passed index /// public bool AutoDispose { get; set; } = false; /// /// Gets or sets the amount of last mats to keep cached when is enabled /// public ushort AutoDisposeKeepLast { get; set; } = 0; /// /// Gets the cache mat array /// private Mat[][] MatCache { get; } /// /// Gets or sets the action to trigger after cache the initial /// public Action? AfterCacheAction { get; set; } public MatCacheManager(FileFormat slicerFile, ushort cacheCount = 0, byte elementsPerCache = 1) : this(slicerFile, 0, slicerFile.LastLayerIndex, cacheCount, elementsPerCache) { } public MatCacheManager(FileFormat slicerFile, uint collectionSize, ushort cacheCount = 0, byte elementsPerCache = 1) : this(slicerFile, 0, collectionSize-1, cacheCount, elementsPerCache) { } public MatCacheManager(Operation operation, ushort cacheCount = 0, byte elementsPerCache = 1) : this(operation.SlicerFile, operation.LayerIndexStart, operation.LayerIndexEnd, cacheCount, elementsPerCache) {} public MatCacheManager(FileFormat slicerFile, uint layerIndexStart, uint layerIndexEnd, ushort cacheCount = 0, byte elementsPerCache = 1) { if (cacheCount == 0) cacheCount = (ushort)(Environment.ProcessorCount * 5); if (layerIndexEnd == 0) layerIndexEnd = slicerFile.LayerCount > 0 ? slicerFile.LayerCount - 1 : 0; SlicerFile = slicerFile; LayerIndexStart = layerIndexStart; LayerIndexEnd = layerIndexEnd; CacheCount = cacheCount; ElementsPerCache = elementsPerCache; MatCache = new Mat[CollectionSize][]; if (layerIndexStart == 0) UseRawIndexInsteadOfLayerIndex = true; } /// /// Gets the cache index from a layer index, required when layer range is not 0 started /// /// /// private uint LayerIndexToCacheIndex(uint layerIndex) { return UseRawIndexInsteadOfLayerIndex ? layerIndex : layerIndex - LayerIndexStart; } /// /// Gets the layer index from the cache index, required when layer range is not 0 started /// /// /// private uint CacheIndexToLayerIndex(uint index) { return UseRawIndexInsteadOfLayerIndex ? index : index + LayerIndexStart; } /// /// Gets all cached mat's given an index /// /// /// public Mat[] Get(uint layerIndex) { uint index = LayerIndexToCacheIndex(layerIndex); if (MatCache[index] is null) { if (AutoDispose) // Dispose as go { ClearButKeep(layerIndex, AutoDisposeKeepLast); } int fromLayerIndex = (int)layerIndex; int toLayerIndex = (int)Math.Min(LayerIndexEnd+1, layerIndex + CacheCount); if (!Direction) { toLayerIndex = fromLayerIndex + 1; fromLayerIndex = Math.Max((int)LayerIndexStart, fromLayerIndex - CacheCount); } Parallel.For(fromLayerIndex, toLayerIndex, CoreSettings.ParallelOptions, currentLayerIndex => { var currentCacheIndex = LayerIndexToCacheIndex((uint)currentLayerIndex); if (MatCache[currentCacheIndex] is not null) return; // Already cached MatCache[currentCacheIndex] = new Mat[ElementsPerCache]; MatCache[currentCacheIndex][0] = SlicerFile[currentLayerIndex].LayerMat; if (Flip != FlipDirection.None) { CvInvoke.Flip(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], (FlipType)Flip); } if (Rotate != RotateDirection.None) { CvInvoke.Rotate(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], (RotateFlags) Rotate); } if (StripAntiAliasing) { CvInvoke.Threshold(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], 127, byte.MaxValue, ThresholdType.Binary); } AfterCacheAction?.Invoke(MatCache[currentCacheIndex]); }); } return MatCache[index]; } public Mat Get(uint layerIndex, byte elementIndex) { return Get(layerIndex)[elementIndex]; } public Mat Get1(uint layerIndex) { return Get(layerIndex)[0]; } public (Mat mat1, Mat mat2) Get2(uint layerIndex) { var mats = Get(layerIndex); return (mats[0], mats[1]); } public (Mat mat1, Mat mat2, Mat mat3) Get3(uint layerIndex) { var mats = Get(layerIndex); return (mats[0], mats[1], mats[2]); } public void GetAndConsumeWork(uint layerIndex, Action action) { try { action.Invoke(Get(layerIndex)); } catch (Exception e) { Debug.WriteLine(e); throw; } finally { Consume(layerIndex); } } public void GetAndConsumeWork(uint layerIndex, Action action) { try { action.Invoke(Get1(layerIndex)); } catch (Exception e) { Debug.WriteLine(e); throw; } finally { Consume(layerIndex); } } public void GetAndConsumeWork(uint layerIndex, byte elementIndex, Action action) { try { action.Invoke(Get(layerIndex)[elementIndex]); } catch (Exception e) { Debug.WriteLine(e); throw; } finally { Consume(layerIndex); } } /// /// Consume and dispose the cached Mats for given layer index /// /// public void Consume(uint layerIndex) { var index = LayerIndexToCacheIndex(layerIndex); if(MatCache[index] is null) return; for (int i = 0; i < MatCache[index].Length; i++) { MatCache[index][i]?.Dispose(); } MatCache[index] = null!; } /// /// Clears and dispose the cache but keep the selected index and optionally the last n indexes /// /// /// public void ClearButKeep(uint layerIndex, ushort keepLast = 0) { if (Direction) { for (int i = (int)layerIndex - 1 - keepLast; i >= LayerIndexStart; i--) Consume((uint)i); } else { for (int i = (int)layerIndex + 1 + keepLast; i <= LayerIndexEnd; i++) Consume((uint)i); } } /// /// Clears and dispose all the cache /// public void Clear() { for (uint i = 0; i < MatCache.Length; i++) { Consume(CacheIndexToLayerIndex(i)); } } /// public void Dispose() { Clear(); } }