diff --git a/.DS_Store b/.DS_Store index 0040be41..01228294 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Damselfly.Core.ImageProcessing/Damselfly.Core.ImageProcessing.csproj b/Damselfly.Core.ImageProcessing/Damselfly.Core.ImageProcessing.csproj index f4c6a3a8..ac47c334 100644 --- a/Damselfly.Core.ImageProcessing/Damselfly.Core.ImageProcessing.csproj +++ b/Damselfly.Core.ImageProcessing/Damselfly.Core.ImageProcessing.csproj @@ -15,6 +15,7 @@ + diff --git a/Damselfly.Core.ImageProcessing/ImageMagickProcessor.cs b/Damselfly.Core.ImageProcessing/ImageMagickProcessor.cs index 10fbde29..997f5f95 100644 --- a/Damselfly.Core.ImageProcessing/ImageMagickProcessor.cs +++ b/Damselfly.Core.ImageProcessing/ImageMagickProcessor.cs @@ -11,7 +11,6 @@ public class ImageMagickProcessor : IImageProcessor private const string graphicsMagickExe = "gm"; - // SkiaSharp doesn't handle .heic files... yet private static readonly string[] s_imageExtensions = { ".jpg", ".jpeg", ".png", ".heic", ".tif", ".tiff", ".webp", ".arw", ".cr3" }; private static bool imAvailable; private readonly bool s_useGraphicsMagick = false; // GM doesn't support HEIC yet. diff --git a/Damselfly.Core.ImageProcessing/ImageProcessorFactory.cs b/Damselfly.Core.ImageProcessing/ImageProcessorFactory.cs index 913ee880..27ee54f7 100644 --- a/Damselfly.Core.ImageProcessing/ImageProcessorFactory.cs +++ b/Damselfly.Core.ImageProcessing/ImageProcessorFactory.cs @@ -7,12 +7,14 @@ public class ImageProcessorFactory : IImageProcessorFactory private readonly ImageMagickProcessor imProcessor; private readonly ImageSharpProcessor isharpProcessor; private readonly SkiaSharpProcessor skiaProcessor; + private readonly MagickNetProcessor magickNetProcessor; public ImageProcessorFactory() { skiaProcessor = new SkiaSharpProcessor(); isharpProcessor = new ImageSharpProcessor(); imProcessor = new ImageMagickProcessor(); + magickNetProcessor = new MagickNetProcessor(); } public void SetContentPath(string path) @@ -47,11 +49,17 @@ public IHashProvider GetHashProvider() if ( ImageSharpProcessor.SupportedFileExtensions.Any(x => x.Equals(fileExtension, StringComparison.OrdinalIgnoreCase)) ) return isharpProcessor; + // Magick.Net - As of 12-Aug-2024, it can do thumbs for 100 images in about 45 seconds. + // Main advantage: it can also handle HEIC, and is native + if ( MagickNetProcessor.SupportedFileExtensions.Any(x => + x.Equals(fileExtension, StringComparison.OrdinalIgnoreCase)) ) return magickNetProcessor; + // ImageMagick last, because of the complexities of spawning a child process. // As of 12-Aug-2021, it can do thumbs for 100 images in about 33 seconds. // Main advantage: it can also handle HEIC - if ( ImageMagickProcessor.SupportedFileExtensions.Any(x => - x.Equals(fileExtension, StringComparison.OrdinalIgnoreCase)) ) return imProcessor; + // Mar 2024 - disable this in preference to Magick.Net + //if ( ImageMagickProcessor.SupportedFileExtensions.Any(x => + // x.Equals(fileExtension, StringComparison.OrdinalIgnoreCase)) ) return imProcessor; return null; } diff --git a/Damselfly.Core.ImageProcessing/MagickNetProcessor.cs b/Damselfly.Core.ImageProcessing/MagickNetProcessor.cs new file mode 100644 index 00000000..528538b0 --- /dev/null +++ b/Damselfly.Core.ImageProcessing/MagickNetProcessor.cs @@ -0,0 +1,93 @@ +using Damselfly.Core.DbModels.Images; +using Damselfly.Core.Interfaces; +using Damselfly.Core.Utils; +using Damselfly.Shared.Utils; +using ImageMagick; + +namespace Damselfly.Core.ImageProcessing; + +public class MagickNetProcessor : IImageProcessor +{ + public static ICollection SupportedFileExtensions => SupportedExtensions(); + + private static ICollection SupportedExtensions() + { + // Interrogate Magick.Net for a list of supported file extensions where we can read and write the image + var formats = MagickNET.SupportedFormats.Where( format => format.SupportsReading && format.SupportsWriting ) + .Select( format => $".{format.Format.ToString().ToLower()}").ToList(); + + return formats; + } + + /// + /// Convert the files to thumbnails using Magick.Net + /// + /// Source. + /// Sizes. + public async Task CreateThumbs(FileInfo source, IDictionary destFiles) + { + // This processor doesn't support hash creation + IImageProcessResult result = new ImageProcessResult { ThumbsGenerated = false, ImageHash = string.Empty }; + + using var image = new MagickImage(); + + var load = new Stopwatch("MagickNetLoad"); + + await image.ReadAsync(source.FullName); + image.AutoOrient(); + + load.Stop(); + + var thumbs = new Stopwatch("MagickNetThumbs"); + + foreach ( var pair in destFiles ) + { + var dest = pair.Key; + var config = pair.Value; + + var targetRatio = (decimal)config.width / (decimal)config.height; + var currentRatio = (decimal)image.Width/(decimal)image.Height; + + bool widthIsLongest = currentRatio > targetRatio; + + var intermediateSize = widthIsLongest + ? new MagickGeometry { Width = (int)(config.height * currentRatio), Height = config.height } + : new MagickGeometry { Width = config.width, Height = (int)(config.width / currentRatio) }; + + Logging.LogTrace("Generating thumbnail for {0}: {1}x{2}", source.Name, config.width, config.height); + + image.Resize(intermediateSize); + intermediateSize.IgnoreAspectRatio = false; + + if( currentRatio != targetRatio && config.cropToRatio) + { + var size = new MagickGeometry(config.width, config.height); + + image.Crop(size, Gravity.Center); + } + + await image.WriteAsync(dest.FullName); + + result.ThumbsGenerated = true; + } + + thumbs.Stop(); + + return result; + } + + public Task TransformDownloadImage(string input, Stream output, IExportSettings config) + { + throw new NotImplementedException(); + } + + public Task GetCroppedFile(FileInfo source, int x, int y, int width, int height, FileInfo destFile) + { + throw new NotImplementedException(); + } + + public Task CropImage(FileInfo source, int x, int y, int width, int height, Stream stream) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Damselfly.Web.Client/Pages/ImagePage.razor b/Damselfly.Web.Client/Pages/ImagePage.razor index 41e6bb6a..bbb68392 100644 --- a/Damselfly.Web.Client/Pages/ImagePage.razor +++ b/Damselfly.Web.Client/Pages/ImagePage.razor @@ -212,6 +212,7 @@ CurrentImage = await imageCache.GetCachedImage(imageId); navContext.CurrentImage = CurrentImage; + // TODO: Fix nullref here ImageUrl = CurrentImage.ThumbUrl(ThumbSize.Medium); ImageUrlHighRes = CurrentImage.ThumbUrl(ThumbSize.ExtraLarge); ; diff --git a/Damselfly.Web.Client/wwwroot/version.js b/Damselfly.Web.Client/wwwroot/version.js index dae24d53..070aaaa9 100644 --- a/Damselfly.Web.Client/wwwroot/version.js +++ b/Damselfly.Web.Client/wwwroot/version.js @@ -1 +1 @@ -const CACHE_VERSION='4.1.3-20240321104506' +const CACHE_VERSION='4.1.3-20240326071412' diff --git a/Damselfly.Web.Server/Controllers/PeopleController.cs b/Damselfly.Web.Server/Controllers/PeopleController.cs index a8f539dc..038e0df8 100644 --- a/Damselfly.Web.Server/Controllers/PeopleController.cs +++ b/Damselfly.Web.Server/Controllers/PeopleController.cs @@ -14,9 +14,7 @@ namespace Damselfly.Web.Server.Controllers; [ApiController] [Route("/api/people")] public class PeopleController( ImageRecognitionService _aiService, - IPeopleService _peopleService, - ILogger _logger, - ImageCache _imageCache) : ControllerBase + IPeopleService _peopleService) : ControllerBase { [HttpGet("/api/person/{personId}")] public async Task GetPerson( int personId ) diff --git a/Damselfly.Web.Server/Controllers/StaticDataController.cs b/Damselfly.Web.Server/Controllers/StaticDataController.cs index a9498da1..fc2527c7 100644 --- a/Damselfly.Web.Server/Controllers/StaticDataController.cs +++ b/Damselfly.Web.Server/Controllers/StaticDataController.cs @@ -12,8 +12,7 @@ namespace Damselfly.Web.Server.Controllers; //[Authorize(Policy = PolicyDefinitions.s_IsLoggedIn)] [ApiController] [Route("/api/data")] -public class StaticDataController( ILogger _logger, - MetaDataService _metaDataService, +public class StaticDataController( MetaDataService _metaDataService, ICachedDataService _cachedData, StatisticsService _stats) : ControllerBase { diff --git a/Directory.Packages.props b/Directory.Packages.props index a958b666..cbc5dda1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,6 +20,7 @@ +