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