V12: Update ImageSharp V3 and Add Legacy V2 Project (#14216)
* Rename old imagesharp to v2 * Add Ronalds PR as imagesharp * Ensure that we use V3 by default
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the ImageSharp middleware options.
|
||||
/// </summary>
|
||||
/// <seealso cref="IConfigureOptions{ImageSharpMiddlewareOptions}" />
|
||||
public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions<ImageSharpMiddlewareOptions>
|
||||
{
|
||||
private readonly Configuration _configuration;
|
||||
private readonly ImagingSettings _imagingSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureImageSharpMiddlewareOptions" /> class.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The ImageSharp configuration.</param>
|
||||
/// <param name="imagingSettings">The Umbraco imaging settings.</param>
|
||||
public ConfigureImageSharpMiddlewareOptions(Configuration configuration, IOptions<ImagingSettings> imagingSettings)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_imagingSettings = imagingSettings.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<int>(
|
||||
context.Commands.GetValueOrDefault(ResizeWebProcessor.Width),
|
||||
context.Culture);
|
||||
if (width <= 0 || width > _imagingSettings.Resize.MaxWidth)
|
||||
{
|
||||
context.Commands.Remove(ResizeWebProcessor.Width);
|
||||
}
|
||||
|
||||
var height = context.Parser.ParseValue<int>(
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the ImageSharp physical file system cache options.
|
||||
/// </summary>
|
||||
/// <seealso cref="IConfigureOptions{PhysicalFileSystemCacheOptions}" />
|
||||
public sealed class ConfigurePhysicalFileSystemCacheOptions : IConfigureOptions<PhysicalFileSystemCacheOptions>
|
||||
{
|
||||
private readonly IHostEnvironment _hostEnvironment;
|
||||
private readonly ImagingSettings _imagingSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigurePhysicalFileSystemCacheOptions" /> class.
|
||||
/// </summary>
|
||||
/// <param name="imagingSettings">The Umbraco imaging settings.</param>
|
||||
/// <param name="hostEnvironment">The host environment.</param>
|
||||
public ConfigurePhysicalFileSystemCacheOptions(
|
||||
IOptions<ImagingSettings> imagingSettings,
|
||||
IHostEnvironment hostEnvironment)
|
||||
{
|
||||
_imagingSettings = imagingSettings.Value;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(PhysicalFileSystemCacheOptions options)
|
||||
{
|
||||
options.CacheFolder = _hostEnvironment.MapPathContentRoot(_imagingSettings.Cache.CacheFolder);
|
||||
options.CacheFolderDepth = _imagingSettings.Cache.CacheFolderDepth;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Allows the cropping of images.
|
||||
/// </summary>
|
||||
/// <seealso cref="SixLabors.ImageSharp.Web.Processors.IImageWebProcessor" />
|
||||
public class CropWebProcessor : IImageWebProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// The command constant for the crop coordinates.
|
||||
/// </summary>
|
||||
public const string Coordinates = "cc";
|
||||
|
||||
/// <summary>
|
||||
/// The command constant for the resize orientation handling mode.
|
||||
/// </summary>
|
||||
public const string Orient = "orient";
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> Commands { get; } = new[] { Coordinates, Orient };
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<float[]>(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<bool>(commands.GetValueOrDefault(Orient), culture))
|
||||
{
|
||||
return ExifOrientationMode.Unknown;
|
||||
}
|
||||
|
||||
image.TryGetExifOrientation(out var orientation);
|
||||
|
||||
return orientation;
|
||||
}
|
||||
}
|
||||
16
src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageSharpComposer.cs
Normal file
16
src/Umbraco.Cms.Imaging.ImageSharp.V2/ImageSharpComposer.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Imaging.ImageSharp.V2;
|
||||
|
||||
/// <summary>
|
||||
/// Adds imaging support using ImageSharp/ImageSharp.Web.
|
||||
/// </summary>
|
||||
/// <seealso cref="Umbraco.Cms.Core.Composing.IComposer" />
|
||||
public sealed class ImageSharpComposer : IComposer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Compose(IUmbracoBuilder builder)
|
||||
=> builder.AddUmbracoImageSharp();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> SupportedImageFileTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageSharpDimensionExtractor" /> class.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration.</param>
|
||||
public ImageSharpDimensionExtractor(Configuration configuration)
|
||||
{
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
|
||||
SupportedImageFileTypes = configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<ushort>? 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp.
|
||||
/// </summary>
|
||||
/// <seealso cref="IImageUrlGenerator" />
|
||||
public sealed class ImageSharpImageUrlGenerator : IImageUrlGenerator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> SupportedImageFileTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The ImageSharp configuration.</param>
|
||||
public ImageSharpImageUrlGenerator(Configuration configuration)
|
||||
: this(configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
|
||||
/// </summary>
|
||||
/// <param name="supportedImageFileTypes">The supported image file types/extensions.</param>
|
||||
/// <remarks>
|
||||
/// This constructor is only used for testing.
|
||||
/// </remarks>
|
||||
internal ImageSharpImageUrlGenerator(IEnumerable<string> supportedImageFileTypes) =>
|
||||
SupportedImageFileTypes = supportedImageFileTypes;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetImageUrl(ImageUrlGenerationOptions? options)
|
||||
{
|
||||
if (options?.ImageUrl == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var queryString = new Dictionary<string, string?>();
|
||||
Dictionary<string, StringValues> 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<string, StringValues> 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Title>Umbraco CMS - Imaging - ImageSharp</Title>
|
||||
<Description>Adds imaging support using ImageSharp/ImageSharp.Web to Umbraco CMS.</Description>
|
||||
<!-- TODO: Enable when final version is shipped (because there's currently no previous version) -->
|
||||
<EnablePackageValidation>false</EnablePackageValidation>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="2.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Umbraco.Tests.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Image Sharp with Umbraco settings
|
||||
/// </summary>
|
||||
public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder builder)
|
||||
{
|
||||
// Add default ImageSharp configuration and service implementations
|
||||
builder.Services.AddSingleton(Configuration.Default);
|
||||
builder.Services.AddUnique<IImageDimensionExtractor, ImageSharpDimensionExtractor>();
|
||||
|
||||
builder.Services.AddSingleton<IImageUrlGenerator, ImageSharpImageUrlGenerator>();
|
||||
|
||||
builder.Services.AddImageSharp()
|
||||
// Replace default image provider
|
||||
.ClearProviders()
|
||||
.AddProvider<WebRootImageProvider>()
|
||||
// Add custom processors
|
||||
.AddProcessor<CropWebProcessor>();
|
||||
|
||||
// Configure middleware
|
||||
builder.Services.AddTransient<IConfigureOptions<ImageSharpMiddlewareOptions>, ConfigureImageSharpMiddlewareOptions>();
|
||||
|
||||
// Configure cache options
|
||||
builder.Services.AddTransient<IConfigureOptions<PhysicalFileSystemCacheOptions>, ConfigurePhysicalFileSystemCacheOptions>();
|
||||
|
||||
// Important we handle image manipulations before the static files, otherwise the querystring is just ignored
|
||||
builder.Services.Configure<UmbracoPipelineOptions>(options =>
|
||||
{
|
||||
options.AddFilter(new UmbracoPipelineFilter(nameof(ImageSharpComposer))
|
||||
{
|
||||
PrePipeline = prePipeline => prePipeline.UseImageSharp()
|
||||
});
|
||||
});
|
||||
|
||||
return builder.Services;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<float[]>(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,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Imaging.ImageSharp;
|
||||
|
||||
|
||||
@@ -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<ushort>? orientation = imageInfo.Metadata.ExifProfile?.GetValue(ExifTag.Orientation);
|
||||
if (orientation is not null)
|
||||
if (imageInfo.Metadata.ExifProfile?.TryGetValue(ExifTag.Orientation, out IExifValue<ushort>? orientation) == true)
|
||||
{
|
||||
if (orientation.DataType == ExifDataType.Short)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
<PropertyGroup>
|
||||
<Title>Umbraco CMS - Imaging - ImageSharp</Title>
|
||||
<Description>Adds imaging support using ImageSharp/ImageSharp.Web to Umbraco CMS.</Description>
|
||||
<!-- TODO: Enable when final version is shipped (because there's currently no previous version) -->
|
||||
<EnablePackageValidation>false</EnablePackageValidation>
|
||||
<EnablePackageValidation>true</EnablePackageValidation>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="2.0.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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;
|
||||
|
||||
10
umbraco.sln
10
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
|
||||
|
||||
Reference in New Issue
Block a user