diff --git a/src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigureImageSharpMiddlewareOptions.cs
new file mode 100644
index 0000000000..17b735891c
--- /dev/null
+++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigureImageSharpMiddlewareOptions.cs
@@ -0,0 +1,87 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Headers;
+using Microsoft.Extensions.Options;
+using Microsoft.Net.Http.Headers;
+using SixLabors.ImageSharp;
+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.V2;
+
+///
+/// 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.V2/ConfigurePhysicalFileSystemCacheOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ConfigurePhysicalFileSystemCacheOptions.cs
new file mode 100644
index 0000000000..2c70afc3bc
--- /dev/null
+++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/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.V2;
+
+///
+/// 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.V2/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageProcessors/CropWebProcessor.cs
new file mode 100644
index 0000000000..a02f497af2
--- /dev/null
+++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageProcessors/CropWebProcessor.cs
@@ -0,0 +1,87 @@
+using System.Globalization;
+using System.Numerics;
+using Microsoft.Extensions.Logging;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Web;
+using SixLabors.ImageSharp.Web.Commands;
+using SixLabors.ImageSharp.Web.Processors;
+
+namespace Umbraco.Cms.Imaging.ImageSharp.V2.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.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.V2/ImageSharpComposer.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageSharpComposer.cs
new file mode 100644
index 0000000000..5ec2a1f120
--- /dev/null
+++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageSharpComposer.cs
@@ -0,0 +1,16 @@
+using Umbraco.Cms.Core.Composing;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Imaging.ImageSharp.V2;
+
+///
+/// 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.V2/Media/ImageSharpDimensionExtractor.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpDimensionExtractor.cs
new file mode 100644
index 0000000000..d96f4f7f32
--- /dev/null
+++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpDimensionExtractor.cs
@@ -0,0 +1,67 @@
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using Umbraco.Cms.Core.Media;
+using Size = System.Drawing.Size;
+
+namespace Umbraco.Cms.Imaging.ImageSharp.V2.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;
+
+ IImageInfo imageInfo = Image.Identify(_configuration, 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(IImageInfo imageInfo)
+ => GetExifOrientation(imageInfo) switch
+ {
+ ExifOrientationMode.LeftTop
+ or ExifOrientationMode.RightTop
+ or ExifOrientationMode.RightBottom
+ or ExifOrientationMode.LeftBottom => true,
+ _ => false,
+ };
+
+ private static ushort GetExifOrientation(IImageInfo imageInfo)
+ {
+ IExifValue? orientation = imageInfo.Metadata.ExifProfile?.GetValue(ExifTag.Orientation);
+ if (orientation is not null)
+ {
+ if (orientation.DataType == ExifDataType.Short)
+ {
+ return orientation.Value;
+ }
+
+ return Convert.ToUInt16(orientation.Value);
+ }
+
+ return ExifOrientationMode.Unknown;
+ }
+}
diff --git a/src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpImageUrlGenerator.cs
new file mode 100644
index 0000000000..0bcd9ad749
--- /dev/null
+++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Media/ImageSharpImageUrlGenerator.cs
@@ -0,0 +1,106 @@
+using System.Globalization;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Primitives;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Web.Processors;
+using Umbraco.Cms.Core.Media;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Imaging.ImageSharp.V2.ImageProcessors;
+using static Umbraco.Cms.Core.Models.ImageUrlGenerationOptions;
+
+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.
+///
+///
+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.V2/Umbraco.Cms.Imaging.ImageSharp.V2.csproj b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Umbraco.Cms.Imaging.ImageSharp.V2.csproj
new file mode 100644
index 0000000000..0eb87fbda7
--- /dev/null
+++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/Umbraco.Cms.Imaging.ImageSharp.V2.csproj
@@ -0,0 +1,23 @@
+
+
+ Umbraco CMS - Imaging - ImageSharp
+ Adds imaging support using ImageSharp/ImageSharp.Web to Umbraco CMS.
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_Parameter1>Umbraco.Tests.UnitTests
+
+
+
diff --git a/src/Umbraco.Cms.Imaging.ImageSharp.V2/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Imaging.ImageSharp.V2/UmbracoBuilderExtensions.cs
new file mode 100644
index 0000000000..078a5b61d6
--- /dev/null
+++ b/src/Umbraco.Cms.Imaging.ImageSharp.V2/UmbracoBuilderExtensions.cs
@@ -0,0 +1,54 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using SixLabors.ImageSharp;
+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.V2.ImageProcessors;
+using Umbraco.Cms.Imaging.ImageSharp.V2.Media;
+using Umbraco.Cms.Web.Common.ApplicationBuilder;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Imaging.ImageSharp.V2;
+
+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/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs
index 8daa1b689b..aaaade3544 100644
--- a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs
+++ b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs
@@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Headers;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
-using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Web.Commands;
using SixLabors.ImageSharp.Web.Middleware;
using SixLabors.ImageSharp.Web.Processors;
diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs
index eda49fa9d0..0107ae7d20 100644
--- a/src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs
+++ b/src/Umbraco.Cms.Imaging.ImageSharp/ImageProcessors/CropWebProcessor.cs
@@ -1,10 +1,7 @@
using System.Globalization;
using System.Numerics;
using Microsoft.Extensions.Logging;
-using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
-using SixLabors.ImageSharp.Processing;
-using SixLabors.ImageSharp.Web;
using SixLabors.ImageSharp.Web.Commands;
using SixLabors.ImageSharp.Web.Processors;
@@ -48,7 +45,8 @@ public class CropWebProcessor : IImageWebProcessor
private static Rectangle? GetCropRectangle(FormattedImage image, CommandCollection commands, CommandParser parser, CultureInfo culture)
{
var coordinates = parser.ParseValue(commands.GetValueOrDefault(Coordinates), culture);
- if (coordinates.Length != 4 ||
+ if (coordinates is null ||
+ coordinates.Length != 4 ||
(coordinates[0] == 0 && coordinates[1] == 0 && coordinates[2] == 0 && coordinates[3] == 0))
{
return null;
@@ -64,7 +62,7 @@ public class CropWebProcessor : IImageWebProcessor
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();
+ Size size = image.Image.Size;
return Rectangle.Round(RectangleF.FromLTRB(
MathF.Min(xy1.X, xy2.X) * size.Width,
diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs
index 9a77bc28b2..357a125562 100644
--- a/src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs
+++ b/src/Umbraco.Cms.Imaging.ImageSharp/ImageSharpComposer.cs
@@ -1,6 +1,5 @@
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
-using Umbraco.Extensions;
namespace Umbraco.Cms.Imaging.ImageSharp;
diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs
index 409b6e2726..0ec90bb358 100644
--- a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs
+++ b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpDimensionExtractor.cs
@@ -1,4 +1,4 @@
-using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using Umbraco.Cms.Core.Media;
using Size = System.Drawing.Size;
@@ -28,31 +28,35 @@ public sealed class ImageSharpDimensionExtractor : IImageDimensionExtractor
{
Size? size = null;
- IImageInfo imageInfo = Image.Identify(_configuration, stream);
- if (imageInfo != null)
+ if (stream is not null)
{
- size = IsExifOrientationRotated(imageInfo)
- ? new Size(imageInfo.Height, imageInfo.Width)
- : new Size(imageInfo.Width, imageInfo.Height);
+ 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(IImageInfo imageInfo)
+ private static bool IsExifOrientationRotated(ImageInfo imageInfo)
=> GetExifOrientation(imageInfo) switch
{
- ExifOrientationMode.LeftTop
- or ExifOrientationMode.RightTop
- or ExifOrientationMode.RightBottom
- or ExifOrientationMode.LeftBottom => true,
+ ExifOrientationMode.LeftTop or ExifOrientationMode.RightTop or ExifOrientationMode.RightBottom or ExifOrientationMode.LeftBottom => true,
_ => false,
};
- private static ushort GetExifOrientation(IImageInfo imageInfo)
+ private static ushort GetExifOrientation(ImageInfo imageInfo)
{
- IExifValue? orientation = imageInfo.Metadata.ExifProfile?.GetValue(ExifTag.Orientation);
- if (orientation is not null)
+ if (imageInfo.Metadata.ExifProfile?.TryGetValue(ExifTag.Orientation, out IExifValue? orientation) == true)
{
if (orientation.DataType == ExifDataType.Short)
{
diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs
index ad76603187..d0f3b0d55a 100644
--- a/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs
+++ b/src/Umbraco.Cms.Imaging.ImageSharp/Media/ImageSharpImageUrlGenerator.cs
@@ -1,7 +1,6 @@
using System.Globalization;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
-using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Web.Processors;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj
index 0eb87fbda7..e4eb1cd938 100644
--- a/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj
+++ b/src/Umbraco.Cms.Imaging.ImageSharp/Umbraco.Cms.Imaging.ImageSharp.csproj
@@ -2,13 +2,12 @@
Umbraco CMS - Imaging - ImageSharp
Adds imaging support using ImageSharp/ImageSharp.Web to Umbraco CMS.
-
- false
+ true
-
-
+
+
diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs
index 4bd50034ab..7af10cdefa 100644
--- a/src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Cms.Imaging.ImageSharp/UmbracoBuilderExtensions.cs
@@ -1,6 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Web.Caching;
using SixLabors.ImageSharp.Web.DependencyInjection;
using SixLabors.ImageSharp.Web.Middleware;
diff --git a/umbraco.sln b/umbraco.sln
index 236e8627ba..b90c7d5ea2 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
@@ -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