|
|
|
@ -7,6 +7,7 @@ |
|
|
|
|
|
|
|
using System; |
|
|
|
using System.IO; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using SixLabors.ImageSharp; |
|
|
|
using SixLabors.ImageSharp.Formats.Jpeg; |
|
|
|
@ -19,76 +20,89 @@ namespace Squidex.Infrastructure.Assets.ImageSharp |
|
|
|
{ |
|
|
|
public sealed class ImageSharpAssetThumbnailGenerator : IAssetThumbnailGenerator |
|
|
|
{ |
|
|
|
public Task CreateThumbnailAsync(Stream source, Stream destination, ResizeOptions options) |
|
|
|
private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(Math.Max(Environment.ProcessorCount / 4, 1)); |
|
|
|
|
|
|
|
public async Task CreateThumbnailAsync(Stream source, Stream destination, ResizeOptions options) |
|
|
|
{ |
|
|
|
Guard.NotNull(source, nameof(source)); |
|
|
|
Guard.NotNull(destination, nameof(destination)); |
|
|
|
Guard.NotNull(options, nameof(options)); |
|
|
|
|
|
|
|
if (!options.IsValid) |
|
|
|
{ |
|
|
|
source.CopyTo(destination); |
|
|
|
|
|
|
|
return Task.CompletedTask; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var w = options.Width ?? 0; |
|
|
|
var h = options.Height ?? 0; |
|
|
|
|
|
|
|
using (var image = Image.Load(source, out var format)) |
|
|
|
{ |
|
|
|
var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format); |
|
|
|
|
|
|
|
if (encoder == null) |
|
|
|
{ |
|
|
|
throw new NotSupportedException(); |
|
|
|
} |
|
|
|
|
|
|
|
if (options.Quality.HasValue && (encoder is JpegEncoder || !options.KeepFormat)) |
|
|
|
{ |
|
|
|
encoder = new JpegEncoder { Quality = options.Quality.Value }; |
|
|
|
} |
|
|
|
await semaphoreSlim.WaitAsync(); |
|
|
|
|
|
|
|
image.Mutate(x => x.AutoOrient()); |
|
|
|
|
|
|
|
if (w > 0 || h > 0) |
|
|
|
try |
|
|
|
{ |
|
|
|
using (var image = Image.Load(source, out var format)) |
|
|
|
{ |
|
|
|
var isCropUpsize = options.Mode == ResizeMode.CropUpsize; |
|
|
|
var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format); |
|
|
|
|
|
|
|
if (!Enum.TryParse<ISResizeMode>(options.Mode.ToString(), true, out var resizeMode)) |
|
|
|
if (encoder == null) |
|
|
|
{ |
|
|
|
resizeMode = ISResizeMode.Max; |
|
|
|
throw new NotSupportedException(); |
|
|
|
} |
|
|
|
|
|
|
|
if (isCropUpsize) |
|
|
|
if (options.Quality.HasValue && (encoder is JpegEncoder || !options.KeepFormat)) |
|
|
|
{ |
|
|
|
resizeMode = ISResizeMode.Crop; |
|
|
|
encoder = new JpegEncoder { Quality = options.Quality.Value }; |
|
|
|
} |
|
|
|
|
|
|
|
if (w >= image.Width && h >= image.Height && resizeMode == ISResizeMode.Crop && !isCropUpsize) |
|
|
|
{ |
|
|
|
resizeMode = ISResizeMode.BoxPad; |
|
|
|
} |
|
|
|
|
|
|
|
var resizeOptions = new ISResizeOptions { Size = new Size(w, h), Mode = resizeMode }; |
|
|
|
image.Mutate(x => x.AutoOrient()); |
|
|
|
|
|
|
|
if (options.FocusX.HasValue && options.FocusY.HasValue) |
|
|
|
if (w > 0 || h > 0) |
|
|
|
{ |
|
|
|
resizeOptions.CenterCoordinates = new PointF( |
|
|
|
+(options.FocusX.Value / 2f) + 0.5f, |
|
|
|
-(options.FocusY.Value / 2f) + 0.5f |
|
|
|
); |
|
|
|
var isCropUpsize = options.Mode == ResizeMode.CropUpsize; |
|
|
|
|
|
|
|
if (!Enum.TryParse<ISResizeMode>(options.Mode.ToString(), true, out var resizeMode)) |
|
|
|
{ |
|
|
|
resizeMode = ISResizeMode.Max; |
|
|
|
} |
|
|
|
|
|
|
|
if (isCropUpsize) |
|
|
|
{ |
|
|
|
resizeMode = ISResizeMode.Crop; |
|
|
|
} |
|
|
|
|
|
|
|
if (w >= image.Width && h >= image.Height && resizeMode == ISResizeMode.Crop && !isCropUpsize) |
|
|
|
{ |
|
|
|
resizeMode = ISResizeMode.BoxPad; |
|
|
|
} |
|
|
|
|
|
|
|
var resizeOptions = new ISResizeOptions { Size = new Size(w, h), Mode = resizeMode }; |
|
|
|
|
|
|
|
if (options.FocusX.HasValue && options.FocusY.HasValue) |
|
|
|
{ |
|
|
|
resizeOptions.CenterCoordinates = new PointF( |
|
|
|
+(options.FocusX.Value / 2f) + 0.5f, |
|
|
|
-(options.FocusY.Value / 2f) + 0.5f |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
image.Mutate(x => x.Resize(resizeOptions)); |
|
|
|
} |
|
|
|
|
|
|
|
image.Mutate(x => x.Resize(resizeOptions)); |
|
|
|
image.Save(destination, encoder); |
|
|
|
} |
|
|
|
|
|
|
|
image.Save(destination, encoder); |
|
|
|
} |
|
|
|
|
|
|
|
return Task.CompletedTask; |
|
|
|
finally |
|
|
|
{ |
|
|
|
semaphoreSlim.Release(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public Task<ImageInfo?> GetImageInfoAsync(Stream source) |
|
|
|
{ |
|
|
|
Guard.NotNull(source, nameof(source)); |
|
|
|
|
|
|
|
ImageInfo? result = null; |
|
|
|
|
|
|
|
try |
|
|
|
@ -108,22 +122,34 @@ namespace Squidex.Infrastructure.Assets.ImageSharp |
|
|
|
return Task.FromResult(result); |
|
|
|
} |
|
|
|
|
|
|
|
public Task<ImageInfo> FixOrientationAsync(Stream source, Stream destination) |
|
|
|
public async Task<ImageInfo> FixOrientationAsync(Stream source, Stream destination) |
|
|
|
{ |
|
|
|
using (var image = Image.Load(source, out var format)) |
|
|
|
{ |
|
|
|
var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format); |
|
|
|
Guard.NotNull(source, nameof(source)); |
|
|
|
Guard.NotNull(destination, nameof(destination)); |
|
|
|
|
|
|
|
await semaphoreSlim.WaitAsync(); |
|
|
|
|
|
|
|
if (encoder == null) |
|
|
|
try |
|
|
|
{ |
|
|
|
using (var image = Image.Load(source, out var format)) |
|
|
|
{ |
|
|
|
throw new NotSupportedException(); |
|
|
|
} |
|
|
|
var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format); |
|
|
|
|
|
|
|
if (encoder == null) |
|
|
|
{ |
|
|
|
throw new NotSupportedException(); |
|
|
|
} |
|
|
|
|
|
|
|
image.Mutate(x => x.AutoOrient()); |
|
|
|
image.Mutate(x => x.AutoOrient()); |
|
|
|
|
|
|
|
image.Save(destination, encoder); |
|
|
|
image.Save(destination, encoder); |
|
|
|
|
|
|
|
return Task.FromResult(GetImageInfo(image)); |
|
|
|
return GetImageInfo(image); |
|
|
|
} |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
semaphoreSlim.Release(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|