diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 7ce6cb157c..ad51ac5532 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -441,7 +441,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Ddd.Domain.Shared" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Image.Abstractions", "src\Volo.Abp.Image.Abstractions\Volo.Abp.Image.Abstractions.csproj", "{32F3E84B-D02E-42BD-BC5C-0D211564EF30}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Image.Web", "src\Volo.Abp.Image.Web\Volo.Abp.Image.Web.csproj", "{78340A37-219E-4F2D-9AC6-40A7B467EEEC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Image.AspNetCore", "src\Volo.Abp.Image.AspNetCore\Volo.Abp.Image.AspNetCore.csproj", "{78340A37-219E-4F2D-9AC6-40A7B467EEEC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Image.ImageSharp", "src\Volo.Abp.Image.ImageSharp\Volo.Abp.Image.ImageSharp.csproj", "{44467427-E0BE-492C-B9B4-82B362C183C3}" EndProject diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressor.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressor.cs index f2338887a0..40a2357570 100644 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressor.cs +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressor.cs @@ -1,15 +1,21 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; namespace Volo.Abp.Image; -public interface IImageCompressor // TODO: RENAME: IImageCompressorContributor +public interface IImageCompressor { - //TODO: new extension method that works with byte arrays - Task CompressAsync(Stream stream, CancellationToken cancellationToken = default); // TODO: TryCompressAsync & remove CanCompress - - Stream Compress(Stream stream); - - bool CanCompress(IImageFormat imageFormat); + Task> CompressAsync( + Stream stream, + [CanBeNull] string mimeType = null, + bool ignoreExceptions = false, + CancellationToken cancellationToken = default); + + Task> CompressAsync( + byte[] bytes, + [CanBeNull] string mimeType = null, + bool ignoreExceptions = false, + CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressorContributor.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressorContributor.cs new file mode 100644 index 0000000000..bac2d8e0c9 --- /dev/null +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressorContributor.cs @@ -0,0 +1,11 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Abp.Image; + +public interface IImageCompressorContributor +{ + Task> TryCompressAsync(Stream stream, string mimeType = null, CancellationToken cancellationToken = default); + Task> TryCompressAsync(byte[] bytes, string mimeType = null, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressorManager.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressorManager.cs deleted file mode 100644 index f986b2c531..0000000000 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressorManager.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; - -namespace Volo.Abp.Image; - -public interface IImageCompressorManager // TODO: Rename to IImageCompressor -{ - Task CompressAsync( - Stream stream, - [CanBeNull] IImageFormat imageFormat = null, - CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressorSelector.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressorSelector.cs deleted file mode 100644 index a847ecc713..0000000000 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageCompressorSelector.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Volo.Abp.Image; - -public interface IImageCompressorSelector //TODO: Remove, merge to IImageCompressorManager -{ - IImageCompressor FindCompressor(IImageFormat imageFormat); -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageFormat.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageFormat.cs deleted file mode 100644 index e892d980cb..0000000000 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageFormat.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Volo.Abp.Image; - -public interface IImageFormat -{ - string MimeType { get; } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageFormatDetector.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageFormatDetector.cs deleted file mode 100644 index b085706645..0000000000 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageFormatDetector.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.IO; - -namespace Volo.Abp.Image; - -public interface IImageFormatDetector -{ - IImageFormat FindFormat(Stream image); -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizeParameter.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizeParameter.cs deleted file mode 100644 index a53d4f04c9..0000000000 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizeParameter.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Image; - -public interface IImageResizeParameter //TODO: Remove -{ - int? Width { get; } - int? Height { get; } - - ImageResizeMode? Mode { get; } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizer.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizer.cs index a1a7e788f5..d177ceb983 100644 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizer.cs +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizer.cs @@ -1,15 +1,13 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; namespace Volo.Abp.Image; public interface IImageResizer { - Task ResizeAsync(Stream stream, IImageResizeParameter resizeParameter, - CancellationToken cancellationToken = default); //TODO: TryResizeAsync... + Task> ResizeAsync(Stream stream, ImageResizeArgs resizeArgs, [CanBeNull]string mimeType = null, CancellationToken cancellationToken = default); - Stream Resize(Stream stream, IImageResizeParameter resizeParameter); //TODO: Remove - - bool CanResize(IImageFormat imageFormat); //TODO: Discard (merge with TryResizeAsync) + Task> ResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, [CanBeNull] string mimeType = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizerContributor.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizerContributor.cs new file mode 100644 index 0000000000..583afb3132 --- /dev/null +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizerContributor.cs @@ -0,0 +1,12 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Abp.Image; + +public interface IImageResizerContributor +{ + Task> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, string mimeType = null, CancellationToken cancellationToken = default); + + Task> TryResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, string mimeType = null, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizerSelector.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizerSelector.cs deleted file mode 100644 index 92c1fa11be..0000000000 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/IImageResizerSelector.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Volo.Abp.Image; - -public interface IImageResizerSelector -{ - IImageResizer FindResizer(IImageFormat imageFormat); -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageCompressor.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageCompressor.cs new file mode 100644 index 0000000000..f5be0aeb25 --- /dev/null +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageCompressor.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Image; + +public class ImageCompressor : IImageCompressor, ITransientDependency +{ + protected IEnumerable ImageCompressorContributors { get; } + + public ImageCompressor(IEnumerable imageCompressorContributors) + { + ImageCompressorContributors = imageCompressorContributors; + } + + public virtual async Task> CompressAsync(Stream stream, string mimeType = null, bool ignoreExceptions = false, CancellationToken cancellationToken = default) + { + foreach (var imageCompressorContributor in ImageCompressorContributors) + { + var result = await imageCompressorContributor.TryCompressAsync(stream, mimeType, cancellationToken); + if (!result.IsSupported) + { + continue; + } + + if (!ignoreExceptions && result.Exception != null) + { + throw result.Exception; + } + + if (result.IsSuccess) + { + return new ImageProcessResult(result.Result, result.IsSuccess); + } + + return new ImageProcessResult(stream, false); + } + + return new ImageProcessResult(stream, false); + } + + public virtual async Task> CompressAsync(byte[] bytes, string mimeType = null, bool ignoreExceptions = false, CancellationToken cancellationToken = default) + { + foreach (var imageCompressorContributor in ImageCompressorContributors) + { + var result = await imageCompressorContributor.TryCompressAsync(bytes, mimeType, cancellationToken); + if (!result.IsSupported) + { + continue; + } + + if (!ignoreExceptions && result.Exception != null) + { + throw result.Exception; + } + + if (result.IsSuccess) + { + return new ImageProcessResult(result.Result, result.IsSuccess); + } + + return new ImageProcessResult(bytes, false); + } + + return new ImageProcessResult(bytes, false); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageContributorResult.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageContributorResult.cs new file mode 100644 index 0000000000..5d6e016ad5 --- /dev/null +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageContributorResult.cs @@ -0,0 +1,19 @@ +using System; + +namespace Volo.Abp.Image; + +public class ImageContributorResult +{ + public T Result { get; } + public bool IsSuccess { get; } + public bool IsSupported { get; } + public Exception Exception { get; } + + public ImageContributorResult(T result, bool isSuccess, bool isSupported = true, Exception exception = null) + { + Result = result; + IsSuccess = isSuccess; + IsSupported = isSupported; + Exception = exception; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageFormat.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageFormat.cs deleted file mode 100644 index 330f7ecf4d..0000000000 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageFormat.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Volo.Abp.Image; - -public class ImageFormat : IImageFormat -{ - public ImageFormat(string mimeType) - { - MimeType = mimeType; - } - - public string MimeType { get; } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageProcessResult.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageProcessResult.cs new file mode 100644 index 0000000000..a4bc9cfde2 --- /dev/null +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageProcessResult.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.Image; + +public class ImageProcessResult +{ + public T Result { get; } + public bool IsSuccess { get; } + + public ImageProcessResult(T result, bool isSuccess) + { + Result = result; + IsSuccess = isSuccess; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeArgs.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeArgs.cs new file mode 100644 index 0000000000..b3e9b5428c --- /dev/null +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeArgs.cs @@ -0,0 +1,33 @@ +using System; + +namespace Volo.Abp.Image; + +public class ImageResizeArgs +{ + public int Width { get; set; } + + public int Height { get; set; } + + public ImageResizeMode Mode { get; set; } = ImageResizeMode.Default; + + public ImageResizeArgs(int? width = null, int? height = null, ImageResizeMode? mode = null) + { + if (mode.HasValue) + { + Mode = mode.Value; + } + + if (width is < 0) + { + throw new ArgumentException("Width cannot be negative!", nameof(width)); + } + + if (height is < 0) + { + throw new ArgumentException("Height cannot be negative!", nameof(height)); + } + + Width = width ?? 0; + Height = height ?? 0; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeMode.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeMode.cs index 5f4f73de87..ab6a6c961a 100644 --- a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeMode.cs +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeMode.cs @@ -9,7 +9,5 @@ public enum ImageResizeMode Max, Crop, Pad, - Fill, - Distort, - Default = Stretch + Default } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeOptions.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeOptions.cs new file mode 100644 index 0000000000..8f8bf432df --- /dev/null +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizeOptions.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Image; + +public class ImageResizeOptions +{ + public ImageResizeMode DefaultResizeMode { get; set; } = ImageResizeMode.None; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizer.cs b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizer.cs new file mode 100644 index 0000000000..36df46b506 --- /dev/null +++ b/framework/src/Volo.Abp.Image.Abstractions/Volo/Abp/Image/ImageResizer.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Image; + +public class ImageResizer : IImageResizer, ITransientDependency +{ + protected IEnumerable ImageResizerContributors { get; } + + protected ImageResizeOptions ImageResizeOptions { get; } + + public ImageResizer(IEnumerable imageResizerContributors, IOptions imageResizeOptions) + { + ImageResizerContributors = imageResizerContributors; + ImageResizeOptions = imageResizeOptions.Value; + } + + public async Task> ResizeAsync(Stream stream, ImageResizeArgs resizeArgs, string mimeType = null, CancellationToken cancellationToken = default) + { + ChangeDefaultResizeMode(resizeArgs); + + foreach (var imageResizerContributor in ImageResizerContributors) + { + var result = await imageResizerContributor.TryResizeAsync(stream, resizeArgs, mimeType, cancellationToken); + if (!result.IsSupported) + { + continue; + } + + if (result.Exception != null) + { + throw result.Exception; + } + + if (result.IsSuccess) + { + return new ImageProcessResult(result.Result, result.IsSuccess); + } + + return new ImageProcessResult(stream, false); + } + + return new ImageProcessResult(stream, false); + } + + public async Task> ResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, string mimeType = null, CancellationToken cancellationToken = default) + { + ChangeDefaultResizeMode(resizeArgs); + + foreach (var imageResizerContributor in ImageResizerContributors) + { + var result = await imageResizerContributor.TryResizeAsync(bytes, resizeArgs, mimeType, cancellationToken); + if (!result.IsSupported) + { + continue; + } + + if (result.Exception != null) + { + throw result.Exception; + } + + if (result.IsSuccess) + { + return new ImageProcessResult(result.Result, result.IsSuccess); + } + + return new ImageProcessResult(bytes, false); + } + + return new ImageProcessResult(bytes, false); + } + + protected virtual void ChangeDefaultResizeMode(ImageResizeArgs resizeArgs) + { + if (resizeArgs.Mode == ImageResizeMode.Default) + { + resizeArgs.Mode = ImageResizeOptions.DefaultResizeMode; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Web/FodyWeavers.xml b/framework/src/Volo.Abp.Image.AspNetCore/FodyWeavers.xml similarity index 100% rename from framework/src/Volo.Abp.Image.Web/FodyWeavers.xml rename to framework/src/Volo.Abp.Image.AspNetCore/FodyWeavers.xml diff --git a/framework/src/Volo.Abp.Image.Web/FodyWeavers.xsd b/framework/src/Volo.Abp.Image.AspNetCore/FodyWeavers.xsd similarity index 100% rename from framework/src/Volo.Abp.Image.Web/FodyWeavers.xsd rename to framework/src/Volo.Abp.Image.AspNetCore/FodyWeavers.xsd diff --git a/framework/src/Volo.Abp.Image.Web/Volo.Abp.Image.Web.csproj b/framework/src/Volo.Abp.Image.AspNetCore/Volo.Abp.Image.AspNetCore.csproj similarity index 67% rename from framework/src/Volo.Abp.Image.Web/Volo.Abp.Image.Web.csproj rename to framework/src/Volo.Abp.Image.AspNetCore/Volo.Abp.Image.AspNetCore.csproj index f8ce10d10c..c6b8286e1a 100644 --- a/framework/src/Volo.Abp.Image.Web/Volo.Abp.Image.Web.csproj +++ b/framework/src/Volo.Abp.Image.AspNetCore/Volo.Abp.Image.AspNetCore.csproj @@ -5,7 +5,7 @@ net7.0 - Volo.Abp.Image.Web + Volo.Abp.Image.AspNetCore $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; false false @@ -17,15 +17,7 @@ - - - - - - - true - content\ - + diff --git a/framework/src/Volo.Abp.Image.Web/Volo.Abp.Json.abppkg.json b/framework/src/Volo.Abp.Image.AspNetCore/Volo.Abp.Json.abppkg.json similarity index 100% rename from framework/src/Volo.Abp.Image.Web/Volo.Abp.Json.abppkg.json rename to framework/src/Volo.Abp.Image.AspNetCore/Volo.Abp.Json.abppkg.json diff --git a/framework/src/Volo.Abp.Image.AspNetCore/Volo/Abp/Image/AbpImageAspNetCoreModule.cs b/framework/src/Volo.Abp.Image.AspNetCore/Volo/Abp/Image/AbpImageAspNetCoreModule.cs new file mode 100644 index 0000000000..a7374bf098 --- /dev/null +++ b/framework/src/Volo.Abp.Image.AspNetCore/Volo/Abp/Image/AbpImageAspNetCoreModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.Image; + +[DependsOn(typeof(AbpImageAbstractionsModule))] +public class AbpImageAspNetCoreModule : AbpModule +{ + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.AspNetCore/Volo/Abp/Image/CompressImageAttribute.cs b/framework/src/Volo.Abp.Image.AspNetCore/Volo/Abp/Image/CompressImageAttribute.cs new file mode 100644 index 0000000000..018d85c6a2 --- /dev/null +++ b/framework/src/Volo.Abp.Image.AspNetCore/Volo/Abp/Image/CompressImageAttribute.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Content; + +namespace Volo.Abp.Image; + +public class CompressImageAttribute : ActionFilterAttribute +{ + public string[] Parameters { get; } + + protected IImageCompressor ImageCompressor { get; set; } + + public CompressImageAttribute(params string[] parameters) + { + Parameters = parameters; + } + + public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var parameters = Parameters.Any() + ? context.ActionArguments.Where(x => Parameters.Contains(x.Key)).ToArray() + : context.ActionArguments.ToArray(); + + ImageCompressor = context.HttpContext.RequestServices.GetRequiredService(); + + foreach (var (key, value) in parameters) + { + object compressedValue = value switch { + IFormFile file => await CompressImageAsync(file), + IRemoteStreamContent remoteStreamContent => await CompressImageAsync(remoteStreamContent), + Stream stream => await CompressImageAsync(stream), + IEnumerable bytes => await CompressImageAsync(bytes.ToArray()), + _ => null + }; + + if (compressedValue != null) + { + context.ActionArguments[key] = compressedValue; + } + } + + await next(); + } + + protected async Task CompressImageAsync(IFormFile file) + { + if(file.ContentType == null || !file.ContentType.StartsWith("image/")) + { + return file; + } + + var result = await ImageCompressor.CompressAsync(file.OpenReadStream(), file.ContentType); + + if (result.IsSuccess) + { + return new FormFile(result.Result, 0, result.Result.Length, file.Name, file.FileName); + } + + return file; + } + + protected async Task CompressImageAsync(IRemoteStreamContent remoteStreamContent) + { + if(remoteStreamContent.ContentType == null || !remoteStreamContent.ContentType.StartsWith("image/")) + { + return remoteStreamContent; + } + + var result = await ImageCompressor.CompressAsync(remoteStreamContent.GetStream(), remoteStreamContent.ContentType); + + if (result.IsSuccess) + { + var fileName = remoteStreamContent.FileName; + var contentType = remoteStreamContent.ContentType; + remoteStreamContent.Dispose(); + return new RemoteStreamContent(result.Result, fileName, contentType); + } + + return remoteStreamContent; + } + + protected async Task CompressImageAsync(Stream stream) + { + var result = await ImageCompressor.CompressAsync(stream); + + if (result.IsSuccess) + { + await stream.DisposeAsync(); + return result.Result; + } + + return stream; + } + + protected async Task CompressImageAsync(byte[] bytes) + { + return (await ImageCompressor.CompressAsync(bytes)).Result; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.AspNetCore/Volo/Abp/Image/ResizeImageAttribute.cs b/framework/src/Volo.Abp.Image.AspNetCore/Volo/Abp/Image/ResizeImageAttribute.cs new file mode 100644 index 0000000000..de7108c75e --- /dev/null +++ b/framework/src/Volo.Abp.Image.AspNetCore/Volo/Abp/Image/ResizeImageAttribute.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Content; + +namespace Volo.Abp.Image; + +public class ResizeImageAttribute : ActionFilterAttribute +{ + public int? Width { get; } + public int? Height { get; } + + public ImageResizeMode Mode { get; set; } + public string[] Parameters { get; } + + protected IImageResizer ImageResizer { get; set; } + + public ResizeImageAttribute(int width, int height, params string[] parameters) + { + Width = width; + Height = height; + Parameters = parameters; + } + + public ResizeImageAttribute(int size, params string[] parameters) + { + Width = size; + Height = size; + Parameters = parameters; + } + + public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var parameters = Parameters.Any() + ? context.ActionArguments.Where(x => Parameters.Contains(x.Key)).ToArray() + : context.ActionArguments.ToArray(); + + ImageResizer = context.HttpContext.RequestServices.GetRequiredService(); + + foreach (var (key, value) in parameters) + { + object resizedValue = value switch { + IFormFile file => await ResizeImageAsync(file), + IRemoteStreamContent remoteStreamContent => await ResizeImageAsync(remoteStreamContent), + Stream stream => await ResizeImageAsync(stream), + IEnumerable bytes => await ResizeImageAsync(bytes.ToArray()), + _ => null + }; + + if (resizedValue != null) + { + context.ActionArguments[key] = resizedValue; + } + } + + await next(); + } + + protected async Task ResizeImageAsync(IFormFile file) + { + if(file.ContentType == null || !file.ContentType.StartsWith("image/")) + { + return file; + } + + var result = await ImageResizer.ResizeAsync(file.OpenReadStream(), new ImageResizeArgs(Width, Height, Mode), file.ContentType); + + if (result.IsSuccess) + { + return new FormFile(result.Result, 0, result.Result.Length, file.Name, file.FileName); + } + + return file; + } + + protected async Task ResizeImageAsync(IRemoteStreamContent remoteStreamContent) + { + if(remoteStreamContent.ContentType == null || !remoteStreamContent.ContentType.StartsWith("image/")) + { + return remoteStreamContent; + } + + var result = await ImageResizer.ResizeAsync(remoteStreamContent.GetStream(), new ImageResizeArgs(Width, Height, Mode), remoteStreamContent.ContentType); + + if (result.IsSuccess) + { + var fileName = remoteStreamContent.FileName; + var contentType = remoteStreamContent.ContentType; + remoteStreamContent.Dispose(); + return new RemoteStreamContent(result.Result, fileName, contentType); + } + + return remoteStreamContent; + } + + protected async Task ResizeImageAsync(Stream stream) + { + var result = await ImageResizer.ResizeAsync(stream, new ImageResizeArgs(Width, Height, Mode)); + + if (result.IsSuccess) + { + await stream.DisposeAsync(); + return result.Result; + } + + return stream; + } + + protected async Task ResizeImageAsync(byte[] bytes) + { + return (await ImageResizer.ResizeAsync(bytes, new ImageResizeArgs(Width, Height, Mode))).Result; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpCompressOptions.cs b/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpCompressOptions.cs new file mode 100644 index 0000000000..4244bfb772 --- /dev/null +++ b/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpCompressOptions.cs @@ -0,0 +1,11 @@ +using SixLabors.ImageSharp.Formats.Png; + +namespace Volo.Abp.Image; + +public class ImageSharpCompressOptions +{ + public int JpegQuality { get; set; } = 60; + public PngCompressionLevel PngCompressionLevel { get; set; } = PngCompressionLevel.BestCompression; + public bool PngIgnoreMetadata { get; set; } = true; + public int WebpQuality { get; set; } = 60; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageCompressor.cs b/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageCompressor.cs deleted file mode 100644 index 72d395b34b..0000000000 --- a/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageCompressor.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Webp; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Image; - -public class ImageSharpImageCompressor : IImageCompressor, ITransientDependency -{ - public async Task CompressAsync(Stream stream, CancellationToken cancellationToken = default) - { - var memoryStream = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken); - - using var image = await SixLabors.ImageSharp.Image.LoadAsync(memoryStream, cancellationToken); - memoryStream.Position = 0; - - var format = await SixLabors.ImageSharp.Image.DetectFormatAsync(memoryStream, cancellationToken); - memoryStream.Position = 0; - - var encoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(format); - switch (encoder) - { - case JpegEncoder jpegEncoder: - jpegEncoder.Quality = 60; - break; - case PngEncoder pngEncoder: - pngEncoder.CompressionLevel = PngCompressionLevel.BestCompression; //TODO: AbpImageSharpOptions (others too) - pngEncoder.IgnoreMetadata = true; - break; - case WebpEncoder webPEncoder: - webPEncoder.Quality = 60; - webPEncoder.UseAlphaCompression = true; - break; - case null: - throw new NotSupportedException($"No encoder available for the given format: {format.Name}"); - } - - await image.SaveAsync(memoryStream, encoder, cancellationToken: cancellationToken); - memoryStream.SetLength(memoryStream.Position); - - return memoryStream; - } - - public Stream Compress(Stream stream) - { - var newStream = stream.CreateMemoryStream(); - using var image = SixLabors.ImageSharp.Image.Load(newStream); - newStream.Position = 0; - var format = SixLabors.ImageSharp.Image.DetectFormat(newStream); - newStream.Position = 0; - var encoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(format); - - switch (encoder) - { - case JpegEncoder jpegEncoder: - jpegEncoder.Quality = 60; - break; - case PngEncoder pngEncoder: - pngEncoder.CompressionLevel = PngCompressionLevel.BestCompression; - pngEncoder.IgnoreMetadata = true; - break; - case WebpEncoder webPEncoder: - webPEncoder.Quality = 60; - webPEncoder.UseAlphaCompression = true; - break; - case null: - throw new NotSupportedException($"No encoder available for provided path: {format.Name}"); - } - - image.Save(newStream, encoder); - newStream.SetLength(newStream.Position); - return newStream; - } - - - public bool CanCompress(IImageFormat format) - { - //TODO: Use MimeTypes (after moving it to Volo.Abp.Core) - return format?.MimeType switch { - "image/jpeg" => true, - "image/png" => true, - "image/webp" => true, - _ => false - }; - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageCompressorContributor.cs b/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageCompressorContributor.cs new file mode 100644 index 0000000000..3ddfcce2ab --- /dev/null +++ b/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageCompressorContributor.cs @@ -0,0 +1,108 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Webp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; + +namespace Volo.Abp.Image; + +public class ImageSharpImageCompressorContributor : IImageCompressorContributor, ITransientDependency +{ + protected ImageSharpCompressOptions Options { get; } + + public ImageSharpImageCompressorContributor(IOptions options) + { + Options = options.Value; + } + + public async Task> TryCompressAsync(Stream stream, string mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) + { + return new ImageContributorResult(stream, false, false); + } + + MemoryStream ms = null; + + try + { + var (image, format) = await SixLabors.ImageSharp.Image.LoadWithFormatAsync(stream, cancellationToken); + + mimeType = format.DefaultMimeType; + + if (!CanCompress(mimeType)) + { + return new ImageContributorResult(stream, false, false); + } + + var encoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(format); + + switch (encoder) + { + case JpegEncoder jpegEncoder: + jpegEncoder.Quality = Options.JpegQuality; + break; + case PngEncoder pngEncoder: + pngEncoder.CompressionLevel = Options.PngCompressionLevel; + pngEncoder.IgnoreMetadata = Options.PngIgnoreMetadata; + break; + case WebpEncoder webPEncoder: + webPEncoder.Quality = Options.WebpQuality; + webPEncoder.UseAlphaCompression = true; + break; + case null: + throw new NotSupportedException($"No encoder available for the given format: {format.Name}"); + } + + ms = new MemoryStream(); + await image.SaveAsync(ms, encoder, cancellationToken: cancellationToken); + ms.Position = 0; + + return new ImageContributorResult(ms, true); + } + catch (Exception e) + { + ms?.Dispose(); + + return new ImageContributorResult(stream, false, true, e); + } + } + + public async Task> TryCompressAsync(byte[] bytes, string mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) + { + return new ImageContributorResult(bytes, false, false); + } + + using var ms = new MemoryStream(bytes); + var result = await TryCompressAsync(ms, mimeType, cancellationToken); + + if (!result.IsSuccess) + { + return new ImageContributorResult(bytes, result.IsSuccess, result.IsSupported, result.Exception); + } + + var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); + result.Result.Dispose(); + return new ImageContributorResult(newBytes, true); + } + + private static bool CanCompress(string mimeType) + { + return mimeType switch { + MimeTypes.Image.Jpeg => true, + MimeTypes.Image.Png => true, + MimeTypes.Image.Webp => true, + _ => false + }; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageFormatDetector.cs b/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageFormatDetector.cs deleted file mode 100644 index eab8874b21..0000000000 --- a/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageFormatDetector.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.IO; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Image; - -public class ImageSharpImageFormatDetector : IImageFormatDetector, ITransientDependency -{ - public IImageFormat FindFormat(Stream stream) - { - var format = SixLabors.ImageSharp.Image.DetectFormat(stream); - return new ImageFormat(format.DefaultMimeType); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageResizer.cs b/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageResizer.cs deleted file mode 100644 index ff20fae798..0000000000 --- a/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageResizer.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Image; - -public class ImageSharpImageResizer : IImageResizer, ITransientDependency -{ - - public async Task ResizeAsync(Stream stream, IImageResizeParameter resizeParameter, CancellationToken cancellationToken = default) - { - var newStream = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken); - using var image = await SixLabors.ImageSharp.Image.LoadAsync(newStream, cancellationToken); - ApplyMode(image, resizeParameter); - newStream.Position = 0; - var format = await SixLabors.ImageSharp.Image.DetectFormatAsync(newStream, cancellationToken); - newStream.Position = 0; - await image.SaveAsync(newStream, format, cancellationToken: cancellationToken); - newStream.SetLength(newStream.Position); - newStream.Position = 0; - return newStream; - } - - public Stream Resize(Stream stream, IImageResizeParameter resizeParameter) - { - var newStream = stream.CreateMemoryStream(); - using var image = SixLabors.ImageSharp.Image.Load(newStream); - ApplyMode(image, resizeParameter); - newStream.Position = 0; - var format = SixLabors.ImageSharp.Image.DetectFormat(newStream); - newStream.Position = 0; - image.Save(newStream, format); - newStream.SetLength(newStream.Position); - newStream.Position = 0; - return newStream; - } - - public bool CanResize(IImageFormat imageFormat) - { - return imageFormat?.MimeType switch - { - "image/jpeg" => true, - "image/png" => true, - "image/gif" => true, - "image/bmp" => true, - "image/tiff" => true, - _ => false - }; - } - - private void ApplyMode(SixLabors.ImageSharp.Image image, IImageResizeParameter resizeParameter) - { - var width = resizeParameter.Width ?? image.Width; - var height = resizeParameter.Height ?? image.Height; - - var defaultResizeOptions = new ResizeOptions { Size = new SixLabors.ImageSharp.Size(width, height) }; - - switch (resizeParameter.Mode) - { - case null: - case ImageResizeMode.None: - image.Mutate(x => x.Resize(defaultResizeOptions)); - break; - case ImageResizeMode.Stretch: - defaultResizeOptions.Mode = ResizeMode.Stretch; - image.Mutate(x => x.Resize(defaultResizeOptions)); - break; - case ImageResizeMode.BoxPad: - defaultResizeOptions.Mode = ResizeMode.BoxPad; - image.Mutate(x => x.Resize(defaultResizeOptions)); - break; - case ImageResizeMode.Min: - defaultResizeOptions.Mode = ResizeMode.Min; - image.Mutate(x => x.Resize(defaultResizeOptions)); - break; - case ImageResizeMode.Max: - defaultResizeOptions.Mode = ResizeMode.Max; - image.Mutate(x => x.Resize(defaultResizeOptions)); - break; - case ImageResizeMode.Crop: - defaultResizeOptions.Mode = ResizeMode.Crop; - image.Mutate(x => x.Resize(defaultResizeOptions)); - break; - case ImageResizeMode.Pad: - defaultResizeOptions.Mode = ResizeMode.Pad; - image.Mutate(x => x.Resize(defaultResizeOptions)); - break; - case ImageResizeMode.Fill: - defaultResizeOptions.Mode = ResizeMode.Stretch; - image.Mutate(x => x.Resize(defaultResizeOptions)); - break; - default: - throw new NotSupportedException("Resize mode " + resizeParameter.Mode + "is not supported!"); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageResizerContributor.cs b/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageResizerContributor.cs new file mode 100644 index 0000000000..5e27549b52 --- /dev/null +++ b/framework/src/Volo.Abp.Image.ImageSharp/Volo/Abp/Image/ImageSharpImageResizerContributor.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; + +namespace Volo.Abp.Image; + +public class ImageSharpImageResizerContributor : IImageResizerContributor, ITransientDependency +{ + public async Task> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, + string mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) + { + return new ImageContributorResult(stream, false, false); + } + + var (image, format) = await SixLabors.ImageSharp.Image.LoadWithFormatAsync(stream, cancellationToken); + + mimeType = format.DefaultMimeType; + + if (!CanResize(mimeType)) + { + return new ImageContributorResult(stream, false, false); + } + + var size = new Size(); + if (resizeArgs.Width > 0) + { + size.Width = resizeArgs.Width; + } + + if (resizeArgs.Height > 0) + { + size.Height = resizeArgs.Height; + } + + var defaultResizeOptions = new ResizeOptions { Size = size }; + + MemoryStream ms = null; + try + { + if (ResizeModeMap.TryGetValue(resizeArgs.Mode, out var resizeMode)) + { + defaultResizeOptions.Mode = resizeMode; + image.Mutate(x => x.Resize(defaultResizeOptions)); + } + else + { + throw new NotSupportedException("Resize mode " + resizeArgs.Mode + "is not supported!"); + } + + ms = new MemoryStream(); + await image.SaveAsync(ms, format, cancellationToken: cancellationToken); + ms.SetLength(ms.Position); + ms.Position = 0; + + return new ImageContributorResult(ms, true); + } + catch (Exception e) + { + ms?.Dispose(); + return new ImageContributorResult(stream, false, true, e); + } + } + + public async Task> TryResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, + string mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) + { + return new ImageContributorResult(bytes, false, false); + } + + using var ms = new MemoryStream(bytes); + var result = await TryResizeAsync(ms, resizeArgs, mimeType, cancellationToken); + + if (!result.IsSuccess) + { + return new ImageContributorResult(bytes, result.IsSuccess, result.IsSupported, result.Exception); + } + + var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); + result.Result.Dispose(); + + return new ImageContributorResult(newBytes, true); + } + + private static bool CanResize(string mimeType) + { + return mimeType switch { + MimeTypes.Image.Jpeg => true, + MimeTypes.Image.Png => true, + MimeTypes.Image.Gif => true, + MimeTypes.Image.Bmp => true, + MimeTypes.Image.Tiff => true, + _ => false + }; + } + + private readonly static Dictionary ResizeModeMap = new() { + { ImageResizeMode.None, ResizeMode.Crop }, + { ImageResizeMode.Stretch, ResizeMode.Stretch }, + { ImageResizeMode.BoxPad, ResizeMode.BoxPad }, + { ImageResizeMode.Min, ResizeMode.Min }, + { ImageResizeMode.Max, ResizeMode.Max }, + { ImageResizeMode.Crop, ResizeMode.Crop }, + { ImageResizeMode.Pad, ResizeMode.Pad } + }; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageCompressor.cs b/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageCompressor.cs deleted file mode 100644 index 8c0745e5aa..0000000000 --- a/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageCompressor.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using ImageMagick; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Image; - -public class MagickImageCompressor : IImageCompressor, ITransientDependency -{ - private readonly ImageOptimizer _optimizer = new(); - - public async Task CompressAsync(Stream stream, CancellationToken cancellationToken = default) - { - var newStream = await stream.CreateMemoryStreamAsync(cancellationToken:cancellationToken); - try //TODO: Remove try/catch - { - _optimizer.IsSupported(newStream); - newStream.Position = 0; - _optimizer.Compress(newStream); - } - catch - { - // ignored - } - return newStream; - } - - public Stream Compress(Stream stream) - { - var newStream = stream.CreateMemoryStream(); - try - { - _optimizer.IsSupported(newStream); - newStream.Position = 0; - _optimizer.Compress(newStream); - } - catch - { - // ignored - } - return newStream; - } - - public bool CanCompress(IImageFormat imageFormat) - { - return imageFormat?.MimeType switch - { - "image/jpeg" => true, - "image/png" => true, - "image/gif" => true, - "image/bmp" => true, - "image/tiff" => true, - _ => false - }; - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageCompressorContributor.cs b/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageCompressorContributor.cs new file mode 100644 index 0000000000..c801e29ee2 --- /dev/null +++ b/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageCompressorContributor.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using ImageMagick; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; + +namespace Volo.Abp.Image; + +public class MagickImageCompressorContributor : IImageCompressorContributor, ITransientDependency +{ + protected MagickNetCompressOptions Options { get; } + + private readonly ImageOptimizer _optimizer; + + public MagickImageCompressorContributor(IOptions options) + { + Options = options.Value; + _optimizer = new ImageOptimizer { + OptimalCompression = Options.OptimalCompression, IgnoreUnsupportedFormats = Options.IgnoreUnsupportedFormats + }; + } + + private static bool CanCompress(string mimeType) + { + return mimeType switch { + MimeTypes.Image.Jpeg => true, + MimeTypes.Image.Png => true, + MimeTypes.Image.Gif => true, + MimeTypes.Image.Bmp => true, + MimeTypes.Image.Tiff => true, + _ => false + }; + } + + public async Task> TryCompressAsync(Stream stream, string mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) + { + return new ImageContributorResult(stream, false, false); + } + + MemoryStream ms = null; + try + { + ms = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken); + + if (!_optimizer.IsSupported(ms)) + { + return new ImageContributorResult(stream, false, false); + } + + Func compressFunc; + + if (Options.Lossless) + { + compressFunc = _optimizer.LosslessCompress; + } + else + { + compressFunc = _optimizer.Compress; + } + + if (compressFunc(ms)) + { + return new ImageContributorResult(ms, true); + } + + ms.Dispose(); + return new ImageContributorResult(stream, false); + } + catch (Exception e) + { + ms?.Dispose(); + return new ImageContributorResult(stream, false, false, e); + } + } + + public async Task> TryCompressAsync(byte[] bytes, string mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) + { + return new ImageContributorResult(bytes, false, false); + } + + using var ms = new MemoryStream(bytes); + var result = await TryCompressAsync(ms, mimeType, cancellationToken); + + if (!result.IsSuccess) + { + result.Result.Dispose(); + return new ImageContributorResult(bytes, result.IsSuccess, result.IsSupported, result.Exception); + } + + var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); + result.Result.Dispose(); + return new ImageContributorResult(newBytes, true); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageFormatDetector.cs b/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageFormatDetector.cs deleted file mode 100644 index 0b9e437a2f..0000000000 --- a/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageFormatDetector.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.IO; -using ImageMagick; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Image; - -public class MagickImageFormatDetector : IImageFormatDetector, ITransientDependency -{ - public IImageFormat FindFormat(Stream image) - { - using var magickImage = new MagickImage(image); - var format = magickImage.FormatInfo; - return format == null ? null : new ImageFormat(format.MimeType ?? string.Empty); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageResizer.cs b/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageResizer.cs deleted file mode 100644 index 6b2a599bb5..0000000000 --- a/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageResizer.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using ImageMagick; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Image; - -public class MagickImageResizer : IImageResizer, ITransientDependency -{ - public async Task ResizeAsync(Stream stream, IImageResizeParameter resizeParameter, - CancellationToken cancellationToken = default) - { - var newStream = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken); - try //TODO: Remove try/catch - { - using var image = new MagickImage(newStream); - ApplyMode(image, resizeParameter); - newStream.Position = 0; - await image.WriteAsync(newStream, cancellationToken); - newStream.SetLength(newStream.Position); - newStream.Position = 0; - } - catch - { - // ignored - } - - return newStream; - } - - public Stream Resize(Stream stream, IImageResizeParameter resizeParameter) - { - var newStream = stream.CreateMemoryStream(); - try - { - using var image = new MagickImage(newStream); - ApplyMode(image, resizeParameter); - newStream.Position = 0; - image.Write(newStream); - newStream.SetLength(newStream.Position); - newStream.Position = 0; - } - catch - { - // ignored - } - - return newStream; - } - - public bool CanResize(IImageFormat imageFormat) - { - return imageFormat?.MimeType switch { - "image/jpeg" => true, - "image/png" => true, - "image/gif" => true, - "image/bmp" => true, - "image/tiff" => true, - _ => false - }; - } - - private void ApplyMode(IMagickImage image, IImageResizeParameter resizeParameter) - { - var width = resizeParameter.Width ?? image.Width; - var height = resizeParameter.Height ?? image.Height; - var defaultMagickGeometry = new MagickGeometry(width, height); - var imageRatio = image.Height / (float)image.Width; - var percentHeight = Math.Abs(height / (float)image.Height); - var percentWidth = Math.Abs(width / (float)image.Width); - var ratio = height / (float)width; - var newWidth = width; - var newHeight = height; - switch (resizeParameter.Mode) - { - case null: - case ImageResizeMode.None: - image.Resize(defaultMagickGeometry); - break; - case ImageResizeMode.Stretch: - defaultMagickGeometry.IgnoreAspectRatio = true; - image.Resize(defaultMagickGeometry); - break; - case ImageResizeMode.Pad: - if (percentHeight < percentWidth) - { - newWidth = (int)Math.Round(image.Width * percentHeight); - } - else - { - newHeight = (int)Math.Round(image.Height * percentWidth); - } - - defaultMagickGeometry.IgnoreAspectRatio = true; - image.Resize(newWidth, newHeight); - image.Extent(width, height, Gravity.Center); - break; - case ImageResizeMode.BoxPad: - int boxPadWidth = width > 0 ? width : (int)Math.Round(image.Width * percentHeight); - int boxPadHeight = height > 0 ? height : (int)Math.Round(image.Height * percentWidth); - - if (image.Width < boxPadWidth && image.Height < boxPadHeight) - { - newWidth = boxPadWidth; - newHeight = boxPadHeight; - } - - image.Resize(newWidth, newHeight); - image.Extent(defaultMagickGeometry, Gravity.Center); - break; - case ImageResizeMode.Max: - - if (imageRatio < ratio) - { - newHeight = (int)(image.Height * percentWidth); - } - else - { - newWidth = (int)(image.Width * percentHeight); - } - - image.Resize(newWidth, newHeight); - break; - case ImageResizeMode.Min: - if (width > image.Width || height > image.Height) - { - newWidth = image.Width; - newHeight = image.Height; - } - else - { - int widthDiff = image.Width - width; - int heightDiff = image.Height - height; - - if (widthDiff > heightDiff) - { - newWidth = (int)Math.Round(height / imageRatio); - } - else if (widthDiff < heightDiff) - { - newHeight = (int)Math.Round(width * imageRatio); - } - else - { - if (height > width) - { - newHeight = (int)Math.Round(image.Height * percentWidth); - } - else - { - newHeight = (int)Math.Round(image.Height * percentWidth); - } - } - } - - image.Resize(newWidth, newHeight); - break; - case ImageResizeMode.Crop: - defaultMagickGeometry.IgnoreAspectRatio = true; - image.Crop(width, height, Gravity.Center); - image.Resize(defaultMagickGeometry); - break; - case ImageResizeMode.Distort: - image.Distort(DistortMethod.Resize, width, height); - break; - case ImageResizeMode.Fill: - defaultMagickGeometry.IgnoreAspectRatio = true; - defaultMagickGeometry.FillArea = true; - image.Resize(defaultMagickGeometry); - break; - default: - throw new NotSupportedException("Resize mode " + resizeParameter.Mode + "is not supported!"); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageResizerContributor.cs b/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageResizerContributor.cs new file mode 100644 index 0000000000..ae3ebfabc4 --- /dev/null +++ b/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickImageResizerContributor.cs @@ -0,0 +1,286 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using ImageMagick; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; + +namespace Volo.Abp.Image; + +public class MagickImageResizerContributor : IImageResizerContributor, ITransientDependency +{ + public async Task> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, + string mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) + { + return new ImageContributorResult(stream, false, false); + } + + MemoryStream ms = null; + try + { + ms = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken); + + using var image = new MagickImage(ms); + + if (string.IsNullOrWhiteSpace(mimeType)) + { + var format = image.FormatInfo; + mimeType = format?.MimeType; + + if (!CanResize(mimeType)) + { + return new ImageContributorResult(stream, false, false); + } + } + + Resize(image, resizeArgs); + + ms.Position = 0; + await image.WriteAsync(ms, cancellationToken); + ms.SetLength(ms.Position); + ms.Position = 0; + return new ImageContributorResult(ms, true); + } + catch (Exception e) + { + ms?.Dispose(); + return new ImageContributorResult(stream, false, true, e); + } + } + + public Task> TryResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, + string mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) + { + return Task.FromResult(new ImageContributorResult(bytes, false, false)); + } + + try + { + using var image = new MagickImage(bytes); + + if (string.IsNullOrWhiteSpace(mimeType)) + { + var format = image.FormatInfo; + mimeType = format?.MimeType; + + if (!CanResize(mimeType)) + { + return Task.FromResult(new ImageContributorResult(bytes, false, false)); + } + } + + Resize(image, resizeArgs); + + return Task.FromResult(new ImageContributorResult(image.ToByteArray(), true)); + } + catch (Exception e) + { + return Task.FromResult(new ImageContributorResult(bytes, false, true, e)); + } + } + + protected virtual bool CanResize(string mimeType) + { + return mimeType switch { + MimeTypes.Image.Jpeg => true, + MimeTypes.Image.Png => true, + MimeTypes.Image.Gif => true, + MimeTypes.Image.Bmp => true, + MimeTypes.Image.Tiff => true, + _ => false + }; + } + + protected virtual void Resize(MagickImage image, ImageResizeArgs resizeParameter) + { + const int min = 1; + int targetWidth = resizeParameter.Width, targetHeight = resizeParameter.Height; + + var sourceWidth = image.Width; + var sourceHeight = image.Height; + + if (targetWidth == 0 && targetHeight > 0) + { + targetWidth = Math.Max(min, (int)Math.Round(sourceWidth * targetHeight / (float)sourceHeight)); + } + + if (targetHeight == 0 && targetWidth > 0) + { + targetHeight = Math.Max(min, (int)Math.Round(sourceHeight * targetWidth / (float)sourceWidth)); + } + + switch (resizeParameter.Mode) + { + case ImageResizeMode.None: + ResizeModeNone(image, targetWidth, targetHeight); + break; + case ImageResizeMode.Stretch: + ResizeStretch(image, targetWidth, targetHeight); + break; + case ImageResizeMode.Pad: + ResizePad(image, targetWidth, targetHeight); + break; + case ImageResizeMode.BoxPad: + ResizeBoxPad(image, targetWidth, targetHeight); + break; + case ImageResizeMode.Max: + ResizeMax(image, targetWidth, targetHeight); + break; + case ImageResizeMode.Min: + ResizeMin(image, targetWidth, targetHeight); + break; + case ImageResizeMode.Crop: + ResizeCrop(image, targetWidth, targetHeight); + break; + default: + throw new NotSupportedException("Resize mode " + resizeParameter.Mode + "is not supported!"); + } + } + + protected virtual void ResizeCrop(MagickImage image, int targetWidth, int targetHeight) + { + var defaultMagickGeometry = new MagickGeometry(targetWidth, targetHeight) { IgnoreAspectRatio = true }; + image.Crop(defaultMagickGeometry, Gravity.Center); + } + + protected virtual void ResizeMin(MagickImage image, int targetWidth, int targetHeight) + { + var sourceWidth = image.Width; + var sourceHeight = image.Height; + + var imageRatio = CalculateRatio(sourceWidth, sourceHeight); + + var percentWidth = CalculatePercent(sourceWidth, targetWidth); + + if (targetWidth > sourceWidth || targetHeight > sourceHeight) + { + targetWidth = sourceWidth; + targetHeight = sourceHeight; + } + else + { + var widthDiff = sourceWidth - targetWidth; + var heightDiff = sourceHeight - targetHeight; + + if (widthDiff > heightDiff) + { + targetWidth = (int)Math.Round(targetHeight / imageRatio); + } + else if (widthDiff < heightDiff) + { + targetHeight = (int)Math.Round(targetWidth * imageRatio); + } + else + { + if (targetHeight > targetWidth) + { + targetWidth = (int)Math.Round(sourceHeight * percentWidth); + } + else + { + targetHeight = (int)Math.Round(sourceHeight * percentWidth); + } + } + } + + image.Resize(targetWidth, targetHeight); + } + + protected virtual void ResizeMax(IMagickImage image, int targetWidth, int targetHeight) + { + var sourceWidth = image.Width; + var sourceHeight = image.Height; + + var imageRatio = CalculateRatio(sourceWidth, sourceHeight); + var ratio = CalculateRatio(targetWidth, targetHeight); + + var percentHeight = CalculatePercent(sourceHeight, targetHeight); + var percentWidth = CalculatePercent(sourceWidth, targetWidth); + + if (imageRatio < ratio) + { + targetHeight = (int)(sourceHeight * percentWidth); + } + else + { + targetWidth = (int)(sourceWidth * percentHeight); + } + + image.Resize(targetWidth, targetHeight); + } + + protected virtual void ResizeBoxPad(MagickImage image, int targetWidth, int targetHeight) + { + var sourceWidth = image.Width; + var sourceHeight = image.Height; + + var percentHeight = CalculatePercent(sourceHeight, targetHeight); + var percentWidth = CalculatePercent(sourceWidth, targetWidth); + + var newWidth = targetWidth; + var newHeight = targetHeight; + + var boxPadWidth = targetWidth > 0 ? targetWidth : (int)Math.Round(sourceWidth * percentHeight); + var boxPadHeight = targetHeight > 0 ? targetHeight : (int)Math.Round(sourceHeight * percentWidth); + + if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) + { + newWidth = boxPadWidth; + newHeight = boxPadHeight; + } + + image.Resize(newWidth, newHeight); + image.Extent(targetWidth, targetHeight, Gravity.Center, MagickColors.Transparent); + } + + protected virtual void ResizePad(MagickImage image, int targetWidth, int targetHeight) + { + var sourceWidth = image.Width; + var sourceHeight = image.Height; + + var percentHeight = CalculatePercent(sourceHeight, targetHeight); + var percentWidth = CalculatePercent(sourceWidth, targetWidth); + + var newWidth = targetWidth; + var newHeight = targetHeight; + + if (percentHeight < percentWidth) + { + newWidth = (int)Math.Round(sourceWidth * percentHeight); + } + else + { + newHeight = (int)Math.Round(sourceHeight * percentWidth); + } + + image.Resize(newWidth, newHeight); + image.Extent(targetWidth, targetHeight, Gravity.Center, MagickColors.Transparent); + } + + protected virtual float CalculatePercent(int imageHeightOrWidth, int heightOrWidth) + { + return heightOrWidth / (float)imageHeightOrWidth; + } + + protected virtual float CalculateRatio(int width, int height) + { + return height / (float)width; + } + + protected virtual void ResizeStretch(IMagickImage image, int targetWidth, int targetHeight) + { + image.Resize(new MagickGeometry(targetWidth, targetHeight) { IgnoreAspectRatio = true }); + } + + protected virtual void ResizeModeNone(IMagickImage image, int targetWidth, int targetHeight) + { + image.Resize(targetWidth, targetHeight); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickNetCompressOptions.cs b/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickNetCompressOptions.cs new file mode 100644 index 0000000000..ab2a2b244d --- /dev/null +++ b/framework/src/Volo.Abp.Image.MagickNet/Volo/Abp/Image/MagickNetCompressOptions.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.Image; + +public class MagickNetCompressOptions +{ + public bool OptimalCompression { get; set; } + public bool IgnoreUnsupportedFormats { get; set; } + public bool Lossless { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/AbpImageCompressAttribute.cs b/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/AbpImageCompressAttribute.cs deleted file mode 100644 index 2ab56ade35..0000000000 --- a/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/AbpImageCompressAttribute.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Filters; -using Volo.Abp.Content; - -namespace Volo.Abp.Image; - -//Rename: CompressImageAttribute -//Also introduce ResizeImageAttribute(...) -public class AbpImageCompressActionFilterAttribute : ActionFilterAttribute -{ - public string[] Parameters { get; } - - public AbpImageCompressActionFilterAttribute(params string[] parameters) - { - Parameters = parameters; - } - - public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) - { - var parameters = Parameters.Any() - ? context.ActionArguments.Where(x => Parameters.Contains(x.Key)).ToArray() - : context.ActionArguments.ToArray(); - - var parameterDescriptors = context.ActionDescriptor.Parameters.OfType(); - var parameterMap = new Dictionary(); - foreach (var parameterDescriptor in parameterDescriptors) - { - var parameter = parameterDescriptor.ParameterInfo; - var attribute = parameter.GetCustomAttribute(); - if (attribute == null) - { - continue; - } - - parameterMap.Add(attribute.Parameter, parameter); - } - - foreach (var (key, value) in parameters) - { - object compressedValue = value switch { - IFormFile file => await file.CompressImageAsync(context.HttpContext.RequestServices), - IRemoteStreamContent remoteStreamContent => await remoteStreamContent.CompressImageAsync(context.HttpContext.RequestServices), - Stream stream => await stream.CompressImageAsync(context.HttpContext.RequestServices), - _ => null - }; - - if (parameterMap.TryGetValue(key, out var parameterInfo)) - { - if (parameterInfo.Name != null) - { - context.ActionArguments.Add(parameterInfo.Name, value); - } - else if (value is IDisposable disposable) - { - disposable.Dispose(); - } - } - else if (value is IDisposable disposable) - { - disposable.Dispose(); - } - - if (compressedValue != null) - { - context.ActionArguments[key] = compressedValue; - } - } - - await next(); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/AbpImageWebModule.cs b/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/AbpImageWebModule.cs deleted file mode 100644 index adb25dec5f..0000000000 --- a/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/AbpImageWebModule.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Volo.Abp.Modularity; - -namespace Volo.Abp.Image; - -[DependsOn(typeof(AbpImageModule))] -public class AbpImageWebModule : AbpModule -{ - -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/AbpOriginalImageAttribute.cs b/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/AbpOriginalImageAttribute.cs deleted file mode 100644 index 3e747028b5..0000000000 --- a/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/AbpOriginalImageAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Volo.Abp.Image; - -[AttributeUsage(AttributeTargets.Parameter)] -public class AbpOriginalImageAttribute : Attribute //TODO: Remove -{ - public AbpOriginalImageAttribute(string parameter) - { - Parameter = parameter; - } - - public string Parameter { get; } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/IFormFileExtensions.cs b/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/IFormFileExtensions.cs deleted file mode 100644 index 634727420a..0000000000 --- a/framework/src/Volo.Abp.Image.Web/Volo/Abp/Image/IFormFileExtensions.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; - -namespace Volo.Abp.Image; - -public static class IFormFileExtensions //TODO: Remove -{ - public async static Task CompressImageAsync( - this IFormFile formFile, - IImageFormat imageFormat, - IImageCompressor imageCompressor, - CancellationToken cancellationToken = default) - { - if (!formFile.ContentType.StartsWith("image")) - { - return formFile; - } - - if (!imageCompressor.CanCompress(imageFormat)) - { - return formFile; - } - - var compressedImageStream = await formFile.OpenReadStream().CompressImageAsync(imageFormat, imageCompressor, cancellationToken); - - var newFormFile = new FormFile(compressedImageStream, 0, compressedImageStream.Length, formFile.Name, - formFile.FileName) { Headers = formFile.Headers }; - - return newFormFile; - } - - public async static Task CompressImageAsync(this IFormFile formFile, IServiceProvider serviceProvider) - { - var imageFormatDetector = serviceProvider.GetRequiredService(); - var imageCompressorSelector = serviceProvider.GetRequiredService(); - var format = imageFormatDetector.FindFormat(formFile.OpenReadStream()); - return await formFile.CompressImageAsync(format, imageCompressorSelector.FindCompressor(format)); - } - - public static async Task ResizeImageAsync( - this IFormFile formFile, - IImageResizeParameter imageResizeParameter, - IImageFormat imageFormat, - IImageResizer imageResizer, - CancellationToken cancellationToken = default) - { - if (!formFile.ContentType.StartsWith("image")) - { - return formFile; - } - - if (!imageResizer.CanResize(imageFormat)) - { - return formFile; - } - - var resizedImageStream = - await formFile.OpenReadStream().ResizeImageAsync(imageResizeParameter, imageFormat, imageResizer, cancellationToken); - - var newFormFile = new FormFile(resizedImageStream, 0, resizedImageStream.Length, formFile.Name, - formFile.FileName) { Headers = formFile.Headers }; - - return newFormFile; - } - - public static Task ResizeImageAsync(this IFormFile formFile, - IImageResizeParameter imageResizeParameter, IServiceProvider serviceProvider) - { - var imageFormatDetector = serviceProvider.GetRequiredService(); - var imageResizerSelector = serviceProvider.GetRequiredService(); - var format = imageFormatDetector.FindFormat(formFile.OpenReadStream()); - return formFile.ResizeImageAsync(imageResizeParameter, format, imageResizerSelector.FindResizer(format)); - } -} \ No newline at end of file