mirror of https://github.com/abpframework/abp.git
42 changed files with 1132 additions and 717 deletions
@ -1,15 +1,21 @@ |
|||||
using System.IO; |
using System.IO; |
||||
using System.Threading; |
using System.Threading; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
namespace Volo.Abp.Image; |
namespace Volo.Abp.Image; |
||||
|
|
||||
public interface IImageCompressor // TODO: RENAME: IImageCompressorContributor
|
public interface IImageCompressor |
||||
{ |
{ |
||||
//TODO: new extension method that works with byte arrays
|
Task<ImageProcessResult<Stream>> CompressAsync( |
||||
Task<Stream> CompressAsync(Stream stream, CancellationToken cancellationToken = default); // TODO: TryCompressAsync & remove CanCompress
|
Stream stream, |
||||
|
[CanBeNull] string mimeType = null, |
||||
Stream Compress(Stream stream); |
bool ignoreExceptions = false, |
||||
|
CancellationToken cancellationToken = default); |
||||
bool CanCompress(IImageFormat imageFormat); |
|
||||
|
Task<ImageProcessResult<byte[]>> CompressAsync( |
||||
|
byte[] bytes, |
||||
|
[CanBeNull] string mimeType = null, |
||||
|
bool ignoreExceptions = false, |
||||
|
CancellationToken cancellationToken = default); |
||||
} |
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Image; |
||||
|
|
||||
|
public interface IImageCompressorContributor |
||||
|
{ |
||||
|
Task<ImageContributorResult<Stream>> TryCompressAsync(Stream stream, string mimeType = null, CancellationToken cancellationToken = default); |
||||
|
Task<ImageContributorResult<byte[]>> TryCompressAsync(byte[] bytes, string mimeType = null, CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -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<Stream> CompressAsync( |
|
||||
Stream stream, |
|
||||
[CanBeNull] IImageFormat imageFormat = null, |
|
||||
CancellationToken cancellationToken = default); |
|
||||
} |
|
||||
@ -1,6 +0,0 @@ |
|||||
namespace Volo.Abp.Image; |
|
||||
|
|
||||
public interface IImageCompressorSelector //TODO: Remove, merge to IImageCompressorManager
|
|
||||
{ |
|
||||
IImageCompressor FindCompressor(IImageFormat imageFormat); |
|
||||
} |
|
||||
@ -1,6 +0,0 @@ |
|||||
namespace Volo.Abp.Image; |
|
||||
|
|
||||
public interface IImageFormat |
|
||||
{ |
|
||||
string MimeType { get; } |
|
||||
} |
|
||||
@ -1,8 +0,0 @@ |
|||||
using System.IO; |
|
||||
|
|
||||
namespace Volo.Abp.Image; |
|
||||
|
|
||||
public interface IImageFormatDetector |
|
||||
{ |
|
||||
IImageFormat FindFormat(Stream image); |
|
||||
} |
|
||||
@ -1,9 +0,0 @@ |
|||||
namespace Volo.Abp.Image; |
|
||||
|
|
||||
public interface IImageResizeParameter //TODO: Remove
|
|
||||
{ |
|
||||
int? Width { get; } |
|
||||
int? Height { get; } |
|
||||
|
|
||||
ImageResizeMode? Mode { get; } |
|
||||
} |
|
||||
@ -1,15 +1,13 @@ |
|||||
using System.IO; |
using System.IO; |
||||
using System.Threading; |
using System.Threading; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
namespace Volo.Abp.Image; |
namespace Volo.Abp.Image; |
||||
|
|
||||
public interface IImageResizer |
public interface IImageResizer |
||||
{ |
{ |
||||
Task<Stream> ResizeAsync(Stream stream, IImageResizeParameter resizeParameter, |
Task<ImageProcessResult<Stream>> ResizeAsync(Stream stream, ImageResizeArgs resizeArgs, [CanBeNull]string mimeType = null, CancellationToken cancellationToken = default); |
||||
CancellationToken cancellationToken = default); //TODO: TryResizeAsync...
|
|
||||
|
|
||||
Stream Resize(Stream stream, IImageResizeParameter resizeParameter); //TODO: Remove
|
Task<ImageProcessResult<byte[]>> ResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, [CanBeNull] string mimeType = null, CancellationToken cancellationToken = default); |
||||
|
|
||||
bool CanResize(IImageFormat imageFormat); //TODO: Discard (merge with TryResizeAsync)
|
|
||||
} |
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Image; |
||||
|
|
||||
|
public interface IImageResizerContributor |
||||
|
{ |
||||
|
Task<ImageContributorResult<Stream>> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, string mimeType = null, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<ImageContributorResult<byte[]>> TryResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, string mimeType = null, CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -1,6 +0,0 @@ |
|||||
namespace Volo.Abp.Image; |
|
||||
|
|
||||
public interface IImageResizerSelector |
|
||||
{ |
|
||||
IImageResizer FindResizer(IImageFormat imageFormat); |
|
||||
} |
|
||||
@ -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<IImageCompressorContributor> ImageCompressorContributors { get; } |
||||
|
|
||||
|
public ImageCompressor(IEnumerable<IImageCompressorContributor> imageCompressorContributors) |
||||
|
{ |
||||
|
ImageCompressorContributors = imageCompressorContributors; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<ImageProcessResult<Stream>> 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<Stream>(result.Result, result.IsSuccess); |
||||
|
} |
||||
|
|
||||
|
return new ImageProcessResult<Stream>(stream, false); |
||||
|
} |
||||
|
|
||||
|
return new ImageProcessResult<Stream>(stream, false); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<ImageProcessResult<byte[]>> 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<byte[]>(result.Result, result.IsSuccess); |
||||
|
} |
||||
|
|
||||
|
return new ImageProcessResult<byte[]>(bytes, false); |
||||
|
} |
||||
|
|
||||
|
return new ImageProcessResult<byte[]>(bytes, false); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Image; |
||||
|
|
||||
|
public class ImageContributorResult<T> |
||||
|
{ |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -1,11 +0,0 @@ |
|||||
namespace Volo.Abp.Image; |
|
||||
|
|
||||
public class ImageFormat : IImageFormat |
|
||||
{ |
|
||||
public ImageFormat(string mimeType) |
|
||||
{ |
|
||||
MimeType = mimeType; |
|
||||
} |
|
||||
|
|
||||
public string MimeType { get; } |
|
||||
} |
|
||||
@ -0,0 +1,13 @@ |
|||||
|
namespace Volo.Abp.Image; |
||||
|
|
||||
|
public class ImageProcessResult<T> |
||||
|
{ |
||||
|
public T Result { get; } |
||||
|
public bool IsSuccess { get; } |
||||
|
|
||||
|
public ImageProcessResult(T result, bool isSuccess) |
||||
|
{ |
||||
|
Result = result; |
||||
|
IsSuccess = isSuccess; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace Volo.Abp.Image; |
||||
|
|
||||
|
public class ImageResizeOptions |
||||
|
{ |
||||
|
public ImageResizeMode DefaultResizeMode { get; set; } = ImageResizeMode.None; |
||||
|
} |
||||
@ -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<IImageResizerContributor> ImageResizerContributors { get; } |
||||
|
|
||||
|
protected ImageResizeOptions ImageResizeOptions { get; } |
||||
|
|
||||
|
public ImageResizer(IEnumerable<IImageResizerContributor> imageResizerContributors, IOptions<ImageResizeOptions> imageResizeOptions) |
||||
|
{ |
||||
|
ImageResizerContributors = imageResizerContributors; |
||||
|
ImageResizeOptions = imageResizeOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public async Task<ImageProcessResult<Stream>> 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<Stream>(result.Result, result.IsSuccess); |
||||
|
} |
||||
|
|
||||
|
return new ImageProcessResult<Stream>(stream, false); |
||||
|
} |
||||
|
|
||||
|
return new ImageProcessResult<Stream>(stream, false); |
||||
|
} |
||||
|
|
||||
|
public async Task<ImageProcessResult<byte[]>> 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<byte[]>(result.Result, result.IsSuccess); |
||||
|
} |
||||
|
|
||||
|
return new ImageProcessResult<byte[]>(bytes, false); |
||||
|
} |
||||
|
|
||||
|
return new ImageProcessResult<byte[]>(bytes, false); |
||||
|
} |
||||
|
|
||||
|
protected virtual void ChangeDefaultResizeMode(ImageResizeArgs resizeArgs) |
||||
|
{ |
||||
|
if (resizeArgs.Mode == ImageResizeMode.Default) |
||||
|
{ |
||||
|
resizeArgs.Mode = ImageResizeOptions.DefaultResizeMode; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.Image; |
||||
|
|
||||
|
[DependsOn(typeof(AbpImageAbstractionsModule))] |
||||
|
public class AbpImageAspNetCoreModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -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<IImageCompressor>(); |
||||
|
|
||||
|
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<byte> bytes => await CompressImageAsync(bytes.ToArray()), |
||||
|
_ => null |
||||
|
}; |
||||
|
|
||||
|
if (compressedValue != null) |
||||
|
{ |
||||
|
context.ActionArguments[key] = compressedValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await next(); |
||||
|
} |
||||
|
|
||||
|
protected async Task<IFormFile> 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<IRemoteStreamContent> 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<Stream> CompressImageAsync(Stream stream) |
||||
|
{ |
||||
|
var result = await ImageCompressor.CompressAsync(stream); |
||||
|
|
||||
|
if (result.IsSuccess) |
||||
|
{ |
||||
|
await stream.DisposeAsync(); |
||||
|
return result.Result; |
||||
|
} |
||||
|
|
||||
|
return stream; |
||||
|
} |
||||
|
|
||||
|
protected async Task<byte[]> CompressImageAsync(byte[] bytes) |
||||
|
{ |
||||
|
return (await ImageCompressor.CompressAsync(bytes)).Result; |
||||
|
} |
||||
|
} |
||||
@ -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<IImageResizer>(); |
||||
|
|
||||
|
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<byte> bytes => await ResizeImageAsync(bytes.ToArray()), |
||||
|
_ => null |
||||
|
}; |
||||
|
|
||||
|
if (resizedValue != null) |
||||
|
{ |
||||
|
context.ActionArguments[key] = resizedValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await next(); |
||||
|
} |
||||
|
|
||||
|
protected async Task<IFormFile> 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<IRemoteStreamContent> 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<Stream> 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<byte[]> ResizeImageAsync(byte[] bytes) |
||||
|
{ |
||||
|
return (await ImageResizer.ResizeAsync(bytes, new ImageResizeArgs(Width, Height, Mode))).Result; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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<Stream> 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 |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
@ -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<ImageSharpCompressOptions> options) |
||||
|
{ |
||||
|
Options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public async Task<ImageContributorResult<Stream>> TryCompressAsync(Stream stream, string mimeType = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) |
||||
|
{ |
||||
|
return new ImageContributorResult<Stream>(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>(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<Stream>(ms, true); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
ms?.Dispose(); |
||||
|
|
||||
|
return new ImageContributorResult<Stream>(stream, false, true, e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<ImageContributorResult<byte[]>> TryCompressAsync(byte[] bytes, string mimeType = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) |
||||
|
{ |
||||
|
return new ImageContributorResult<byte[]>(bytes, false, false); |
||||
|
} |
||||
|
|
||||
|
using var ms = new MemoryStream(bytes); |
||||
|
var result = await TryCompressAsync(ms, mimeType, cancellationToken); |
||||
|
|
||||
|
if (!result.IsSuccess) |
||||
|
{ |
||||
|
return new ImageContributorResult<byte[]>(bytes, result.IsSuccess, result.IsSupported, result.Exception); |
||||
|
} |
||||
|
|
||||
|
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); |
||||
|
result.Result.Dispose(); |
||||
|
return new ImageContributorResult<byte[]>(newBytes, true); |
||||
|
} |
||||
|
|
||||
|
private static bool CanCompress(string mimeType) |
||||
|
{ |
||||
|
return mimeType switch { |
||||
|
MimeTypes.Image.Jpeg => true, |
||||
|
MimeTypes.Image.Png => true, |
||||
|
MimeTypes.Image.Webp => true, |
||||
|
_ => false |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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<Stream> 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!"); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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<ImageContributorResult<Stream>> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, |
||||
|
string mimeType = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) |
||||
|
{ |
||||
|
return new ImageContributorResult<Stream>(stream, false, false); |
||||
|
} |
||||
|
|
||||
|
var (image, format) = await SixLabors.ImageSharp.Image.LoadWithFormatAsync(stream, cancellationToken); |
||||
|
|
||||
|
mimeType = format.DefaultMimeType; |
||||
|
|
||||
|
if (!CanResize(mimeType)) |
||||
|
{ |
||||
|
return new ImageContributorResult<Stream>(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<Stream>(ms, true); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
ms?.Dispose(); |
||||
|
return new ImageContributorResult<Stream>(stream, false, true, e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<ImageContributorResult<byte[]>> TryResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, |
||||
|
string mimeType = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) |
||||
|
{ |
||||
|
return new ImageContributorResult<byte[]>(bytes, false, false); |
||||
|
} |
||||
|
|
||||
|
using var ms = new MemoryStream(bytes); |
||||
|
var result = await TryResizeAsync(ms, resizeArgs, mimeType, cancellationToken); |
||||
|
|
||||
|
if (!result.IsSuccess) |
||||
|
{ |
||||
|
return new ImageContributorResult<byte[]>(bytes, result.IsSuccess, result.IsSupported, result.Exception); |
||||
|
} |
||||
|
|
||||
|
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); |
||||
|
result.Result.Dispose(); |
||||
|
|
||||
|
return new ImageContributorResult<byte[]>(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<ImageResizeMode, ResizeMode> 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 } |
||||
|
}; |
||||
|
} |
||||
@ -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<Stream> 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 |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
@ -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<MagickNetCompressOptions> 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<ImageContributorResult<Stream>> TryCompressAsync(Stream stream, string mimeType = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) |
||||
|
{ |
||||
|
return new ImageContributorResult<Stream>(stream, false, false); |
||||
|
} |
||||
|
|
||||
|
MemoryStream ms = null; |
||||
|
try |
||||
|
{ |
||||
|
ms = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken); |
||||
|
|
||||
|
if (!_optimizer.IsSupported(ms)) |
||||
|
{ |
||||
|
return new ImageContributorResult<Stream>(stream, false, false); |
||||
|
} |
||||
|
|
||||
|
Func<Stream, bool> compressFunc; |
||||
|
|
||||
|
if (Options.Lossless) |
||||
|
{ |
||||
|
compressFunc = _optimizer.LosslessCompress; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
compressFunc = _optimizer.Compress; |
||||
|
} |
||||
|
|
||||
|
if (compressFunc(ms)) |
||||
|
{ |
||||
|
return new ImageContributorResult<Stream>(ms, true); |
||||
|
} |
||||
|
|
||||
|
ms.Dispose(); |
||||
|
return new ImageContributorResult<Stream>(stream, false); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
ms?.Dispose(); |
||||
|
return new ImageContributorResult<Stream>(stream, false, false, e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<ImageContributorResult<byte[]>> TryCompressAsync(byte[] bytes, string mimeType = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) |
||||
|
{ |
||||
|
return new ImageContributorResult<byte[]>(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<byte[]>(bytes, result.IsSuccess, result.IsSupported, result.Exception); |
||||
|
} |
||||
|
|
||||
|
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); |
||||
|
result.Result.Dispose(); |
||||
|
return new ImageContributorResult<byte[]>(newBytes, true); |
||||
|
} |
||||
|
} |
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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<Stream> 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!"); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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<ImageContributorResult<Stream>> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, |
||||
|
string mimeType = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) |
||||
|
{ |
||||
|
return new ImageContributorResult<Stream>(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>(stream, false, false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Resize(image, resizeArgs); |
||||
|
|
||||
|
ms.Position = 0; |
||||
|
await image.WriteAsync(ms, cancellationToken); |
||||
|
ms.SetLength(ms.Position); |
||||
|
ms.Position = 0; |
||||
|
return new ImageContributorResult<Stream>(ms, true); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
ms?.Dispose(); |
||||
|
return new ImageContributorResult<Stream>(stream, false, true, e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task<ImageContributorResult<byte[]>> TryResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, |
||||
|
string mimeType = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) |
||||
|
{ |
||||
|
return Task.FromResult(new ImageContributorResult<byte[]>(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<byte[]>(bytes, false, false)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Resize(image, resizeArgs); |
||||
|
|
||||
|
return Task.FromResult(new ImageContributorResult<byte[]>(image.ToByteArray(), true)); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
return Task.FromResult(new ImageContributorResult<byte[]>(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); |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -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<ControllerParameterDescriptor>(); |
|
||||
var parameterMap = new Dictionary<string, ParameterInfo>(); |
|
||||
foreach (var parameterDescriptor in parameterDescriptors) |
|
||||
{ |
|
||||
var parameter = parameterDescriptor.ParameterInfo; |
|
||||
var attribute = parameter.GetCustomAttribute<AbpOriginalImageAttribute>(); |
|
||||
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(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,9 +0,0 @@ |
|||||
using Volo.Abp.Modularity; |
|
||||
|
|
||||
namespace Volo.Abp.Image; |
|
||||
|
|
||||
[DependsOn(typeof(AbpImageModule))] |
|
||||
public class AbpImageWebModule : AbpModule |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
@ -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; } |
|
||||
} |
|
||||
@ -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<IFormFile> 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<IFormFile> CompressImageAsync(this IFormFile formFile, IServiceProvider serviceProvider) |
|
||||
{ |
|
||||
var imageFormatDetector = serviceProvider.GetRequiredService<IImageFormatDetector>(); |
|
||||
var imageCompressorSelector = serviceProvider.GetRequiredService<IImageCompressorSelector>(); |
|
||||
var format = imageFormatDetector.FindFormat(formFile.OpenReadStream()); |
|
||||
return await formFile.CompressImageAsync(format, imageCompressorSelector.FindCompressor(format)); |
|
||||
} |
|
||||
|
|
||||
public static async Task<IFormFile> 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<IFormFile> ResizeImageAsync(this IFormFile formFile, |
|
||||
IImageResizeParameter imageResizeParameter, IServiceProvider serviceProvider) |
|
||||
{ |
|
||||
var imageFormatDetector = serviceProvider.GetRequiredService<IImageFormatDetector>(); |
|
||||
var imageResizerSelector = serviceProvider.GetRequiredService<IImageResizerSelector>(); |
|
||||
var format = imageFormatDetector.FindFormat(formFile.OpenReadStream()); |
|
||||
return formFile.ResizeImageAsync(imageResizeParameter, format, imageResizerSelector.FindResizer(format)); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue