From f2160fc10129e0c2064382b2ab99fc88d6c7e79d Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Mon, 8 May 2023 10:19:46 +0200 Subject: [PATCH 1/3] Rename old imagesharp to v2 --- .../ConfigureImageSharpMiddlewareOptions.cs | 2 +- .../ConfigurePhysicalFileSystemCacheOptions.cs | 2 +- .../ImageProcessors/CropWebProcessor.cs | 2 +- .../ImageSharpComposer.cs | 2 +- .../Media/ImageSharpDimensionExtractor.cs | 2 +- .../Media/ImageSharpImageUrlGenerator.cs | 4 ++-- .../Umbraco.Cms.Imaging.ImageSharp.V2.csproj} | 0 .../UmbracoBuilderExtensions.cs | 6 +++--- src/Umbraco.Cms/Umbraco.Cms.csproj | 2 +- .../Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj | 2 +- .../ImageProcessors/CropWebProcessorTests.cs | 2 +- .../Media/ImageSharpImageUrlGeneratorTests.cs | 2 +- umbraco.sln | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) rename src/{Umbraco.Cms.Imaging.ImageSharp => Umbraco.Cms.Imaging.ImageSharp.V2}/ConfigureImageSharpMiddlewareOptions.cs (98%) rename src/{Umbraco.Cms.Imaging.ImageSharp => Umbraco.Cms.Imaging.ImageSharp.V2}/ConfigurePhysicalFileSystemCacheOptions.cs (96%) rename src/{Umbraco.Cms.Imaging.ImageSharp => Umbraco.Cms.Imaging.ImageSharp.V2}/ImageProcessors/CropWebProcessor.cs (98%) rename src/{Umbraco.Cms.Imaging.ImageSharp => Umbraco.Cms.Imaging.ImageSharp.V2}/ImageSharpComposer.cs (90%) rename src/{Umbraco.Cms.Imaging.ImageSharp => Umbraco.Cms.Imaging.ImageSharp.V2}/Media/ImageSharpDimensionExtractor.cs (97%) rename src/{Umbraco.Cms.Imaging.ImageSharp => Umbraco.Cms.Imaging.ImageSharp.V2}/Media/ImageSharpImageUrlGenerator.cs (97%) rename src/{Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj => Umbraco.Cms.Imaging.ImageSharp.V2/Umbraco.Cms.Imaging.ImageSharp.V2.csproj} (100%) rename src/{Umbraco.Cms.Imaging.ImageSharp => Umbraco.Cms.Imaging.ImageSharp.V2}/UmbracoBuilderExtensions.cs (93%) diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigureImageSharpMiddlewareOptions.cs similarity index 98% rename from src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs rename to src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigureImageSharpMiddlewareOptions.cs index 8daa1b689b01..17b735891c24 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigureImageSharpMiddlewareOptions.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Web.Processors; using Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Imaging.ImageSharp; +namespace Umbraco.Cms.Imaging.ImageSharp.V2; /// /// Configures the ImageSharp middleware options. diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigurePhysicalFileSystemCacheOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigurePhysicalFileSystemCacheOptions.cs similarity index 96% rename from src/Umbraco.Cms.Imaging.ImageSharp/ConfigurePhysicalFileSystemCacheOptions.cs rename to src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigurePhysicalFileSystemCacheOptions.cs index 3b2cd7f86754..2c70afc3bc09 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigurePhysicalFileSystemCacheOptions.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigurePhysicalFileSystemCacheOptions.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Extensions; -namespace Umbraco.Cms.Imaging.ImageSharp; +namespace Umbraco.Cms.Imaging.ImageSharp.V2; /// /// Configures the ImageSharp physical file system cache options. diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageProcessors/CropWebProcessor.cs similarity index 98% rename from src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs rename to src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageProcessors/CropWebProcessor.cs index eda49fa9d013..a02f497af2bd 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageProcessors/CropWebProcessor.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.Processors; -namespace Umbraco.Cms.Imaging.ImageSharp.ImageProcessors; +namespace Umbraco.Cms.Imaging.ImageSharp.V2.ImageProcessors; /// /// Allows the cropping of images. diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageSharpComposer.cs similarity index 90% rename from src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs rename to src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageSharpComposer.cs index 9a77bc28b294..5ec2a1f12028 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageSharpComposer.cs @@ -2,7 +2,7 @@ using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Extensions; -namespace Umbraco.Cms.Imaging.ImageSharp; +namespace Umbraco.Cms.Imaging.ImageSharp.V2; /// /// Adds imaging support using ImageSharp/ImageSharp.Web. diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpDimensionExtractor.cs similarity index 97% rename from src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs rename to src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpDimensionExtractor.cs index 409b6e272663..d96f4f7f32a6 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpDimensionExtractor.cs @@ -3,7 +3,7 @@ using Umbraco.Cms.Core.Media; using Size = System.Drawing.Size; -namespace Umbraco.Cms.Imaging.ImageSharp.Media; +namespace Umbraco.Cms.Imaging.ImageSharp.V2.Media; public sealed class ImageSharpDimensionExtractor : IImageDimensionExtractor { diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpImageUrlGenerator.cs similarity index 97% rename from src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs rename to src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpImageUrlGenerator.cs index ad766031871e..0bcd9ad749d7 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpImageUrlGenerator.cs @@ -5,10 +5,10 @@ using SixLabors.ImageSharp.Web.Processors; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Imaging.ImageSharp.ImageProcessors; +using Umbraco.Cms.Imaging.ImageSharp.V2.ImageProcessors; using static Umbraco.Cms.Core.Models.ImageUrlGenerationOptions; -namespace Umbraco.Cms.Imaging.ImageSharp.Media; +namespace Umbraco.Cms.Imaging.ImageSharp.V2.Media; /// /// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp. diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Umbraco.Cms.Imaging.ImageSharp.V2.csproj similarity index 100% rename from src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj rename to src/Umbraco.Cms.Imaging.ImageSharp.V2/Umbraco.Cms.Imaging.ImageSharp.V2.csproj diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/UmbracoBuilderExtensions.cs similarity index 93% rename from src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs rename to src/Umbraco.Cms.Imaging.ImageSharp.V2/UmbracoBuilderExtensions.cs index 4bd50034abd9..078a5b61d6bb 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/UmbracoBuilderExtensions.cs @@ -7,12 +7,12 @@ using SixLabors.ImageSharp.Web.Providers; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Media; -using Umbraco.Cms.Imaging.ImageSharp.ImageProcessors; -using Umbraco.Cms.Imaging.ImageSharp.Media; +using Umbraco.Cms.Imaging.ImageSharp.V2.ImageProcessors; +using Umbraco.Cms.Imaging.ImageSharp.V2.Media; using Umbraco.Cms.Web.Common.ApplicationBuilder; using Umbraco.Extensions; -namespace Umbraco.Cms.Imaging.ImageSharp; +namespace Umbraco.Cms.Imaging.ImageSharp.V2; public static class UmbracoBuilderExtensions { diff --git a/src/Umbraco.Cms/Umbraco.Cms.csproj b/src/Umbraco.Cms/Umbraco.Cms.csproj index da6be4c30c7c..8149e390ecea 100644 --- a/src/Umbraco.Cms/Umbraco.Cms.csproj +++ b/src/Umbraco.Cms/Umbraco.Cms.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index 10ad774c34ef..d086617a9184 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs index ea1a51fae670..e85e7592a849 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs @@ -11,7 +11,7 @@ using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.Commands.Converters; using SixLabors.ImageSharp.Web.Middleware; -using Umbraco.Cms.Imaging.ImageSharp.ImageProcessors; +using Umbraco.Cms.Imaging.ImageSharp.V2.ImageProcessors; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.ImageProcessors; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs index 40f28322dccc..f66fa80dc110 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Imaging.ImageSharp.Media; +using Umbraco.Cms.Imaging.ImageSharp.V2.Media; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Media; diff --git a/umbraco.sln b/umbraco.sln index 236e8627ba46..0f0ca27c9caa 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -141,7 +141,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "styles", "styles", "{EA628A build\csharp-docs\umbracotemplate\styles\main.css = build\csharp-docs\umbracotemplate\styles\main.css EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Imaging.ImageSharp", "src\Umbraco.Cms.Imaging.ImageSharp\Umbraco.Cms.Imaging.ImageSharp.csproj", "{C280181E-597B-4AA5-82E7-D7017E928749}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Cms.Imaging.ImageSharp.V2", "src\Umbraco.Cms.Imaging.ImageSharp.V2\Umbraco.Cms.Imaging.ImageSharp.V2.csproj", "{C280181E-597B-4AA5-82E7-D7017E928749}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{05878304-40EB-4F84-B40B-91BDB70DE094}" EndProject From e42c1c3d9f92483058b7ca0eebf03a48069176c6 Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Mon, 8 May 2023 10:52:42 +0200 Subject: [PATCH 2/3] Add Ronalds PR as imagesharp --- .../ConfigureImageSharpMiddlewareOptions.cs | 86 ++++++++++++++ ...ConfigurePhysicalFileSystemCacheOptions.cs | 37 ++++++ .../ImageProcessors/CropWebProcessor.cs | 85 ++++++++++++++ .../ImageSharpComposer.cs | 15 +++ .../Media/ImageSharpDimensionExtractor.cs | 71 ++++++++++++ .../Media/ImageSharpImageUrlGenerator.cs | 105 ++++++++++++++++++ .../Umbraco.Cms.Imaging.ImageSharp.csproj | 22 ++++ .../UmbracoBuilderExtensions.cs | 53 +++++++++ umbraco.sln | 8 ++ 9 files changed, 482 insertions(+) create mode 100644 src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs create mode 100644 src/Umbraco.Cms.Imaging.ImageSharp/ConfigurePhysicalFileSystemCacheOptions.cs create mode 100644 src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs create mode 100644 src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs create mode 100644 src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs create mode 100644 src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs create mode 100644 src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj create mode 100644 src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs new file mode 100644 index 000000000000..aaaade354442 --- /dev/null +++ b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs @@ -0,0 +1,86 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using SixLabors.ImageSharp.Web.Commands; +using SixLabors.ImageSharp.Web.Middleware; +using SixLabors.ImageSharp.Web.Processors; +using Umbraco.Cms.Core.Configuration.Models; + +namespace Umbraco.Cms.Imaging.ImageSharp; + +/// +/// Configures the ImageSharp middleware options. +/// +/// +public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions +{ + private readonly Configuration _configuration; + private readonly ImagingSettings _imagingSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The ImageSharp configuration. + /// The Umbraco imaging settings. + public ConfigureImageSharpMiddlewareOptions(Configuration configuration, IOptions imagingSettings) + { + _configuration = configuration; + _imagingSettings = imagingSettings.Value; + } + + /// + public void Configure(ImageSharpMiddlewareOptions options) + { + options.Configuration = _configuration; + + options.BrowserMaxAge = _imagingSettings.Cache.BrowserMaxAge; + options.CacheMaxAge = _imagingSettings.Cache.CacheMaxAge; + options.CacheHashLength = _imagingSettings.Cache.CacheHashLength; + + // Use configurable maximum width and height + options.OnParseCommandsAsync = context => + { + if (context.Commands.Count == 0) + { + return Task.CompletedTask; + } + + var width = context.Parser.ParseValue( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), + context.Culture); + if (width <= 0 || width > _imagingSettings.Resize.MaxWidth) + { + context.Commands.Remove(ResizeWebProcessor.Width); + } + + var height = context.Parser.ParseValue( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), + context.Culture); + if (height <= 0 || height > _imagingSettings.Resize.MaxHeight) + { + context.Commands.Remove(ResizeWebProcessor.Height); + } + + return Task.CompletedTask; + }; + + // Change Cache-Control header when cache buster value is present + options.OnPrepareResponseAsync = context => + { + if (context.Request.Query.ContainsKey("rnd") || context.Request.Query.ContainsKey("v")) + { + ResponseHeaders headers = context.Response.GetTypedHeaders(); + + CacheControlHeaderValue cacheControl = + headers.CacheControl ?? new CacheControlHeaderValue { Public = true }; + cacheControl.MustRevalidate = false; // ImageSharp enables this by default + cacheControl.Extensions.Add(new NameValueHeaderValue("immutable")); + + headers.CacheControl = cacheControl; + } + + return Task.CompletedTask; + }; + } +} diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigurePhysicalFileSystemCacheOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigurePhysicalFileSystemCacheOptions.cs new file mode 100644 index 000000000000..3b2cd7f86754 --- /dev/null +++ b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigurePhysicalFileSystemCacheOptions.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Web.Caching; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Extensions; + +namespace Umbraco.Cms.Imaging.ImageSharp; + +/// +/// Configures the ImageSharp physical file system cache options. +/// +/// +public sealed class ConfigurePhysicalFileSystemCacheOptions : IConfigureOptions +{ + private readonly IHostEnvironment _hostEnvironment; + private readonly ImagingSettings _imagingSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The Umbraco imaging settings. + /// The host environment. + public ConfigurePhysicalFileSystemCacheOptions( + IOptions imagingSettings, + IHostEnvironment hostEnvironment) + { + _imagingSettings = imagingSettings.Value; + _hostEnvironment = hostEnvironment; + } + + /// + public void Configure(PhysicalFileSystemCacheOptions options) + { + options.CacheFolder = _hostEnvironment.MapPathContentRoot(_imagingSettings.Cache.CacheFolder); + options.CacheFolderDepth = _imagingSettings.Cache.CacheFolderDepth; + } +} diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs new file mode 100644 index 000000000000..0107ae7d204d --- /dev/null +++ b/src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs @@ -0,0 +1,85 @@ +using System.Globalization; +using System.Numerics; +using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Web.Commands; +using SixLabors.ImageSharp.Web.Processors; + +namespace Umbraco.Cms.Imaging.ImageSharp.ImageProcessors; + +/// +/// Allows the cropping of images. +/// +/// +public class CropWebProcessor : IImageWebProcessor +{ + /// + /// The command constant for the crop coordinates. + /// + public const string Coordinates = "cc"; + + /// + /// The command constant for the resize orientation handling mode. + /// + public const string Orient = "orient"; + + /// + public IEnumerable Commands { get; } = new[] { Coordinates, Orient }; + + /// + public FormattedImage Process(FormattedImage image, ILogger logger, CommandCollection commands, CommandParser parser, CultureInfo culture) + { + Rectangle? cropRectangle = GetCropRectangle(image, commands, parser, culture); + if (cropRectangle.HasValue) + { + image.Image.Mutate(x => x.Crop(cropRectangle.Value)); + } + + return image; + } + + /// + public bool RequiresTrueColorPixelFormat(CommandCollection commands, CommandParser parser, CultureInfo culture) => + false; + + private static Rectangle? GetCropRectangle(FormattedImage image, CommandCollection commands, CommandParser parser, CultureInfo culture) + { + var coordinates = parser.ParseValue(commands.GetValueOrDefault(Coordinates), culture); + if (coordinates is null || + coordinates.Length != 4 || + (coordinates[0] == 0 && coordinates[1] == 0 && coordinates[2] == 0 && coordinates[3] == 0)) + { + return null; + } + + // The right and bottom values are actually the distance from those sides, so convert them into real coordinates and transform to correct orientation + var left = Math.Clamp(coordinates[0], 0, 1); + var top = Math.Clamp(coordinates[1], 0, 1); + var right = Math.Clamp(1 - coordinates[2], 0, 1); + var bottom = Math.Clamp(1 - coordinates[3], 0, 1); + var orientation = GetExifOrientation(image, commands, parser, culture); + Vector2 xy1 = ExifOrientationUtilities.Transform(new Vector2(left, top), Vector2.Zero, Vector2.One, orientation); + Vector2 xy2 = ExifOrientationUtilities.Transform(new Vector2(right, bottom), Vector2.Zero, Vector2.One, orientation); + + // Scale points to a pixel based rectangle + Size size = image.Image.Size; + + return Rectangle.Round(RectangleF.FromLTRB( + MathF.Min(xy1.X, xy2.X) * size.Width, + MathF.Min(xy1.Y, xy2.Y) * size.Height, + MathF.Max(xy1.X, xy2.X) * size.Width, + MathF.Max(xy1.Y, xy2.Y) * size.Height)); + } + + private static ushort GetExifOrientation(FormattedImage image, CommandCollection commands, CommandParser parser, CultureInfo culture) + { + if (commands.Contains(Orient) && !parser.ParseValue(commands.GetValueOrDefault(Orient), culture)) + { + return ExifOrientationMode.Unknown; + } + + image.TryGetExifOrientation(out var orientation); + + return orientation; + } +} diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs new file mode 100644 index 000000000000..357a12556234 --- /dev/null +++ b/src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs @@ -0,0 +1,15 @@ +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace Umbraco.Cms.Imaging.ImageSharp; + +/// +/// Adds imaging support using ImageSharp/ImageSharp.Web. +/// +/// +public sealed class ImageSharpComposer : IComposer +{ + /// + public void Compose(IUmbracoBuilder builder) + => builder.AddUmbracoImageSharp(); +} diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs new file mode 100644 index 000000000000..0ec90bb358cc --- /dev/null +++ b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs @@ -0,0 +1,71 @@ +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using Umbraco.Cms.Core.Media; +using Size = System.Drawing.Size; + +namespace Umbraco.Cms.Imaging.ImageSharp.Media; + +public sealed class ImageSharpDimensionExtractor : IImageDimensionExtractor +{ + private readonly Configuration _configuration; + + /// + public IEnumerable SupportedImageFileTypes { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public ImageSharpDimensionExtractor(Configuration configuration) + { + _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + + SupportedImageFileTypes = configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray(); + } + + /// + public Size? GetDimensions(Stream? stream) + { + Size? size = null; + + if (stream is not null) + { + DecoderOptions options = new() + { + Configuration = _configuration, + }; + + ImageInfo imageInfo = Image.Identify(options, stream); + if (imageInfo != null) + { + size = IsExifOrientationRotated(imageInfo) + ? new Size(imageInfo.Height, imageInfo.Width) + : new Size(imageInfo.Width, imageInfo.Height); + } + } + + return size; + } + + private static bool IsExifOrientationRotated(ImageInfo imageInfo) + => GetExifOrientation(imageInfo) switch + { + ExifOrientationMode.LeftTop or ExifOrientationMode.RightTop or ExifOrientationMode.RightBottom or ExifOrientationMode.LeftBottom => true, + _ => false, + }; + + private static ushort GetExifOrientation(ImageInfo imageInfo) + { + if (imageInfo.Metadata.ExifProfile?.TryGetValue(ExifTag.Orientation, out IExifValue? orientation) == true) + { + if (orientation.DataType == ExifDataType.Short) + { + return orientation.Value; + } + + return Convert.ToUInt16(orientation.Value); + } + + return ExifOrientationMode.Unknown; + } +} diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs new file mode 100644 index 000000000000..d0f3b0d55a56 --- /dev/null +++ b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs @@ -0,0 +1,105 @@ +using System.Globalization; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Primitives; +using SixLabors.ImageSharp.Web.Processors; +using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Imaging.ImageSharp.ImageProcessors; +using static Umbraco.Cms.Core.Models.ImageUrlGenerationOptions; + +namespace Umbraco.Cms.Imaging.ImageSharp.Media; + +/// +/// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp. +/// +/// +public sealed class ImageSharpImageUrlGenerator : IImageUrlGenerator +{ + /// + public IEnumerable SupportedImageFileTypes { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The ImageSharp configuration. + public ImageSharpImageUrlGenerator(Configuration configuration) + : this(configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray()) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The supported image file types/extensions. + /// + /// This constructor is only used for testing. + /// + internal ImageSharpImageUrlGenerator(IEnumerable supportedImageFileTypes) => + SupportedImageFileTypes = supportedImageFileTypes; + + /// + public string? GetImageUrl(ImageUrlGenerationOptions? options) + { + if (options?.ImageUrl == null) + { + return null; + } + + var queryString = new Dictionary(); + Dictionary furtherOptions = QueryHelpers.ParseQuery(options.FurtherOptions); + + if (options.Crop is not null) + { + CropCoordinates? crop = options.Crop; + queryString.Add( + CropWebProcessor.Coordinates, + FormattableString.Invariant($"{crop.Left},{crop.Top},{crop.Right},{crop.Bottom}")); + } + + if (options.FocalPoint is not null) + { + queryString.Add(ResizeWebProcessor.Xy, FormattableString.Invariant($"{options.FocalPoint.Left},{options.FocalPoint.Top}")); + } + + if (options.ImageCropMode is not null) + { + queryString.Add(ResizeWebProcessor.Mode, options.ImageCropMode.ToString()?.ToLowerInvariant()); + } + + if (options.ImageCropAnchor is not null) + { + queryString.Add(ResizeWebProcessor.Anchor, options.ImageCropAnchor.ToString()?.ToLowerInvariant()); + } + + if (options.Width is not null) + { + queryString.Add(ResizeWebProcessor.Width, options.Width?.ToString(CultureInfo.InvariantCulture)); + } + + if (options.Height is not null) + { + queryString.Add(ResizeWebProcessor.Height, options.Height?.ToString(CultureInfo.InvariantCulture)); + } + + if (furtherOptions.Remove(FormatWebProcessor.Format, out StringValues format)) + { + queryString.Add(FormatWebProcessor.Format, format[0]); + } + + if (options.Quality is not null) + { + queryString.Add(QualityWebProcessor.Quality, options.Quality?.ToString(CultureInfo.InvariantCulture)); + } + + foreach (KeyValuePair kvp in furtherOptions) + { + queryString.Add(kvp.Key, kvp.Value); + } + + if (options.CacheBusterValue is not null && !string.IsNullOrWhiteSpace(options.CacheBusterValue)) + { + queryString.Add("rnd", options.CacheBusterValue); + } + + return QueryHelpers.AddQueryString(options.ImageUrl, queryString); + } +} diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj new file mode 100644 index 000000000000..e4eb1cd938a0 --- /dev/null +++ b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj @@ -0,0 +1,22 @@ + + + Umbraco CMS - Imaging - ImageSharp + Adds imaging support using ImageSharp/ImageSharp.Web to Umbraco CMS. + true + + + + + + + + + + + + + + <_Parameter1>Umbraco.Tests.UnitTests + + + diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs new file mode 100644 index 000000000000..7af10cdefa21 --- /dev/null +++ b/src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Web.Caching; +using SixLabors.ImageSharp.Web.DependencyInjection; +using SixLabors.ImageSharp.Web.Middleware; +using SixLabors.ImageSharp.Web.Providers; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Imaging.ImageSharp.ImageProcessors; +using Umbraco.Cms.Imaging.ImageSharp.Media; +using Umbraco.Cms.Web.Common.ApplicationBuilder; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Imaging.ImageSharp; + +public static class UmbracoBuilderExtensions +{ + /// + /// Adds Image Sharp with Umbraco settings + /// + public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder builder) + { + // Add default ImageSharp configuration and service implementations + builder.Services.AddSingleton(Configuration.Default); + builder.Services.AddUnique(); + + builder.Services.AddSingleton(); + + builder.Services.AddImageSharp() + // Replace default image provider + .ClearProviders() + .AddProvider() + // Add custom processors + .AddProcessor(); + + // Configure middleware + builder.Services.AddTransient, ConfigureImageSharpMiddlewareOptions>(); + + // Configure cache options + builder.Services.AddTransient, ConfigurePhysicalFileSystemCacheOptions>(); + + // Important we handle image manipulations before the static files, otherwise the querystring is just ignored + builder.Services.Configure(options => + { + options.AddFilter(new UmbracoPipelineFilter(nameof(ImageSharpComposer)) + { + PrePipeline = prePipeline => prePipeline.UseImageSharp() + }); + }); + + return builder.Services; + } +} diff --git a/umbraco.sln b/umbraco.sln index 0f0ca27c9caa..b90c7d5ea2ea 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -149,6 +149,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Api.Delivery", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Api.Common", "src\Umbraco.Cms.Api.Common\Umbraco.Cms.Api.Common.csproj", "{D48B5D6B-82FF-4235-986C-CDE646F41DEC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Imaging.ImageSharp", "src\Umbraco.Cms.Imaging.ImageSharp\Umbraco.Cms.Imaging.ImageSharp.csproj", "{35E3DA10-5549-41DE-B7ED-CC29355BA9FD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -295,6 +297,12 @@ Global {D48B5D6B-82FF-4235-986C-CDE646F41DEC}.Release|Any CPU.Build.0 = Release|Any CPU {D48B5D6B-82FF-4235-986C-CDE646F41DEC}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU {D48B5D6B-82FF-4235-986C-CDE646F41DEC}.SkipTests|Any CPU.Build.0 = Debug|Any CPU + {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.Release|Any CPU.Build.0 = Release|Any CPU + {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU + {35E3DA10-5549-41DE-B7ED-CC29355BA9FD}.SkipTests|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 689b1dd15a70d953fc9aba635a33ccccd5149996 Mon Sep 17 00:00:00 2001 From: Nikolaj Date: Mon, 8 May 2023 11:02:17 +0200 Subject: [PATCH 3/3] Ensure that we use V3 by default --- src/Umbraco.Cms/Umbraco.Cms.csproj | 2 +- tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj | 2 +- .../Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs | 2 +- .../Media/ImageSharpImageUrlGeneratorTests.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Cms/Umbraco.Cms.csproj b/src/Umbraco.Cms/Umbraco.Cms.csproj index 8149e390ecea..da6be4c30c7c 100644 --- a/src/Umbraco.Cms/Umbraco.Cms.csproj +++ b/src/Umbraco.Cms/Umbraco.Cms.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index d086617a9184..10ad774c34ef 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs index e85e7592a849..ea1a51fae670 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs @@ -11,7 +11,7 @@ using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.Commands.Converters; using SixLabors.ImageSharp.Web.Middleware; -using Umbraco.Cms.Imaging.ImageSharp.V2.ImageProcessors; +using Umbraco.Cms.Imaging.ImageSharp.ImageProcessors; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.ImageProcessors; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs index f66fa80dc110..40f28322dccc 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Imaging.ImageSharp.V2.Media; +using Umbraco.Cms.Imaging.ImageSharp.Media; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Media;