mirror of https://github.com/abpframework/abp.git
50 changed files with 1313 additions and 12 deletions
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0</TargetFrameworks> |
|||
<PackageId>Volo.Abp.Image.Abstractions</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
public class AbpImageAbstractionsModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
public interface IImageCompressor |
|||
{ |
|||
Task<Stream> CompressAsync(Stream stream, CancellationToken cancellationToken = default); |
|||
|
|||
Stream Compress(Stream stream); |
|||
|
|||
bool CanCompress(IImageFormat imageFormat); |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.Image; |
|||
|
|||
public interface IImageCompressorSelector |
|||
{ |
|||
IImageCompressor FindCompressor(IImageFormat imageFormat); |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Image; |
|||
|
|||
public interface IImageFormat |
|||
{ |
|||
string Name { get; } |
|||
string MimeType { get; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.IO; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
public interface IImageFormatDetector |
|||
{ |
|||
IImageFormat FindFormat(Stream image); |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Image; |
|||
|
|||
public interface IImageResizeParameter |
|||
{ |
|||
int? Width { get; } |
|||
int? Height { get; } |
|||
|
|||
ImageResizeMode? Mode { get; } |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
public interface IImageResizer |
|||
{ |
|||
Task<Stream> ResizeAsync(Stream stream, IImageResizeParameter resizeParameter, |
|||
CancellationToken cancellationToken = default); |
|||
|
|||
Stream Resize(Stream stream, IImageResizeParameter resizeParameter); |
|||
|
|||
bool CanResize(IImageFormat imageFormat); |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.Image; |
|||
|
|||
public interface IImageResizerSelector |
|||
{ |
|||
IImageResizer FindResizer(IImageFormat imageFormat); |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
namespace Volo.Abp.Image; |
|||
|
|||
public class ImageFormat : IImageFormat |
|||
{ |
|||
public ImageFormat(string name, string mimeType) |
|||
{ |
|||
Name = name; |
|||
MimeType = mimeType; |
|||
} |
|||
|
|||
public string Name { get; } |
|||
public string MimeType { get; } |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
namespace Volo.Abp.Image; |
|||
|
|||
public enum ImageResizeMode |
|||
{ |
|||
None, |
|||
Stretch, |
|||
BoxPad, |
|||
Min, |
|||
Max, |
|||
Crop, |
|||
Pad, |
|||
Fill, |
|||
Distort, |
|||
Default = Stretch |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,24 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;</TargetFrameworks> |
|||
<PackageId>Volo.Abp.Image.ImageSharp</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Image.Abstractions\Volo.Abp.Image.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.4" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
[DependsOn(typeof(AbpImageAbstractionsModule))] |
|||
public class AbpImageSharpModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
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 newStream = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken); |
|||
using var image = await SixLabors.ImageSharp.Image.LoadAsync(newStream, cancellationToken); |
|||
newStream.Position = 0; |
|||
var format = await SixLabors.ImageSharp.Image.DetectFormatAsync(newStream, cancellationToken); |
|||
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}"); |
|||
} |
|||
|
|||
await image.SaveAsync(newStream, encoder, cancellationToken: cancellationToken); |
|||
newStream.SetLength(newStream.Position); |
|||
return newStream; |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
return format?.MimeType switch { |
|||
"image/jpeg" => true, |
|||
"image/png" => true, |
|||
"image/webp" => true, |
|||
_ => false |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
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.Name, format.DefaultMimeType); |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
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,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,24 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0</TargetFrameworks> |
|||
<PackageId>Volo.Abp.Image.MagicNet</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Image.Abstractions\Volo.Abp.Image.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.1.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
[DependsOn(typeof(AbpImageAbstractionsModule))] |
|||
public class AbpImageMagickNetModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
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 |
|||
{ |
|||
_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,15 @@ |
|||
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.Format.ToString(), format.MimeType ?? string.Empty); |
|||
} |
|||
} |
|||
@ -0,0 +1,177 @@ |
|||
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 |
|||
{ |
|||
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,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,31 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<PackageId>Volo.Abp.Image.Web</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<IsPackable>true</IsPackable> |
|||
<OutputType>Library</OutputType> |
|||
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.AspNetCore\Volo.Abp.AspNetCore.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.Image\Volo.Abp.Image.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Content Update="Volo.Abp.Json.abppkg.json"> |
|||
<Pack>true</Pack> |
|||
<PackagePath>content\</PackagePath> |
|||
</Content> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
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; |
|||
|
|||
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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
[DependsOn(typeof(AbpImageModule))] |
|||
public class AbpImageWebModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
[AttributeUsage(AttributeTargets.Parameter)] |
|||
public class AbpOriginalImageAttribute : Attribute |
|||
{ |
|||
public AbpOriginalImageAttribute(string parameter) |
|||
{ |
|||
Parameter = parameter; |
|||
} |
|||
|
|||
public string Parameter { get; } |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
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 |
|||
{ |
|||
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)); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0</TargetFrameworks> |
|||
<PackageId>Volo.Abp.Image</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.Image.Abstractions\Volo.Abp.Image.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
[DependsOn(typeof(AbpImageAbstractionsModule))] |
|||
public class AbpImageModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Content; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
public static class IRemoteStreamContentExtensions |
|||
{ |
|||
public async static Task<IRemoteStreamContent> CompressImageAsync(this IRemoteStreamContent remoteStreamContent, IImageFormat imageFormat, IImageCompressor imageCompressor, CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!imageCompressor.CanCompress(imageFormat)) |
|||
{ |
|||
return remoteStreamContent; |
|||
} |
|||
|
|||
var compressedImageStream = await remoteStreamContent.GetStream().CompressImageAsync(imageFormat, imageCompressor, cancellationToken); |
|||
|
|||
return new RemoteStreamContent(compressedImageStream, remoteStreamContent.FileName, remoteStreamContent.ContentType); |
|||
} |
|||
|
|||
public async static Task<IRemoteStreamContent> CompressImageAsync(this IRemoteStreamContent remoteStreamContent, IServiceProvider serviceProvider, CancellationToken cancellationToken = default) |
|||
{ |
|||
var imageFormatDetector = serviceProvider.GetRequiredService<IImageFormatDetector>(); |
|||
var imageCompressorSelector = serviceProvider.GetRequiredService<IImageCompressorSelector>(); |
|||
var format = imageFormatDetector.FindFormat(remoteStreamContent.GetStream()); |
|||
return await remoteStreamContent.CompressImageAsync(format, imageCompressorSelector.FindCompressor(format), cancellationToken); |
|||
} |
|||
|
|||
public async static Task<IRemoteStreamContent> ResizeImageAsync(this IRemoteStreamContent remoteStreamContent, IImageResizeParameter imageResizeParameter, IImageFormat imageFormat, IImageResizer imageResizer, CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!imageResizer.CanResize(imageFormat)) |
|||
{ |
|||
return remoteStreamContent; |
|||
} |
|||
|
|||
var resizedImageStream = await remoteStreamContent.GetStream().ResizeImageAsync(imageResizeParameter, imageFormat, imageResizer, cancellationToken); |
|||
|
|||
return new RemoteStreamContent(resizedImageStream, remoteStreamContent.FileName, remoteStreamContent.ContentType); |
|||
} |
|||
|
|||
public async static Task<IRemoteStreamContent> ResizeImageAsync(this IRemoteStreamContent remoteStreamContent, IImageResizeParameter imageResizeParameter, IServiceProvider serviceProvider, CancellationToken cancellationToken = default) |
|||
{ |
|||
var imageFormatDetector = serviceProvider.GetRequiredService<IImageFormatDetector>(); |
|||
var imageResizerSelector = serviceProvider.GetRequiredService<IImageResizerSelector>(); |
|||
var format = imageFormatDetector.FindFormat(remoteStreamContent.GetStream()); |
|||
return await remoteStreamContent.ResizeImageAsync(imageResizeParameter, format, imageResizerSelector.FindResizer(format), cancellationToken); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
public class ImageCompressorSelector : IImageCompressorSelector, ITransientDependency |
|||
{ |
|||
private readonly IEnumerable<IImageCompressor> _imageCompressors; |
|||
|
|||
public ImageCompressorSelector(IEnumerable<IImageCompressor> imageCompressors) |
|||
{ |
|||
_imageCompressors = imageCompressors; |
|||
} |
|||
|
|||
public IImageCompressor FindCompressor(IImageFormat imageFormat) |
|||
{ |
|||
return _imageCompressors.FirstOrDefault(x => x.CanCompress(imageFormat)); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
namespace Volo.Abp.Image; |
|||
|
|||
public class ImageResizeParameter : IImageResizeParameter |
|||
{ |
|||
public ImageResizeParameter(int? width = null, int? height = null, ImageResizeMode? mode = null) |
|||
{ |
|||
Mode = mode; |
|||
Width = width; |
|||
Height = height; |
|||
} |
|||
|
|||
public int? Width { get; set; } |
|||
public int? Height { get; set; } |
|||
public ImageResizeMode? Mode { get; set; } |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
public class ImageResizerSelector : IImageResizerSelector, ITransientDependency |
|||
{ |
|||
private readonly IEnumerable<IImageResizer> _imageResizers; |
|||
|
|||
public ImageResizerSelector(IEnumerable<IImageResizer> imageResizers) |
|||
{ |
|||
_imageResizers = imageResizers; |
|||
} |
|||
|
|||
public IImageResizer FindResizer(IImageFormat imageFormat) |
|||
{ |
|||
return _imageResizers.FirstOrDefault(x => x.CanResize(imageFormat)); |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Image; |
|||
|
|||
public static class StreamExtensions |
|||
{ |
|||
public static Stream CompressImage(this Stream stream, IImageFormat imageFormat, IImageCompressor imageCompressor) |
|||
{ |
|||
if (!imageCompressor.CanCompress(imageFormat)) |
|||
{ |
|||
return stream; |
|||
} |
|||
|
|||
return imageCompressor.Compress(stream); |
|||
} |
|||
|
|||
public static Stream CompressImage(this Stream stream, IServiceProvider serviceProvider) |
|||
{ |
|||
var imageFormatDetector = serviceProvider.GetRequiredService<IImageFormatDetector>(); |
|||
var imageCompressorSelector = serviceProvider.GetRequiredService<IImageCompressorSelector>(); |
|||
var format = imageFormatDetector.FindFormat(stream); |
|||
return stream.CompressImage(format, imageCompressorSelector.FindCompressor(format)); |
|||
} |
|||
|
|||
public static Task<Stream> CompressImageAsync(this Stream stream, IImageFormat imageFormat, IImageCompressor imageCompressor, CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!imageCompressor.CanCompress(imageFormat)) |
|||
{ |
|||
return Task.FromResult(stream); |
|||
} |
|||
|
|||
return imageCompressor.CompressAsync(stream, cancellationToken); |
|||
} |
|||
|
|||
public static Task<Stream> CompressImageAsync(this Stream stream, IServiceProvider serviceProvider, CancellationToken cancellationToken = default) |
|||
{ |
|||
var imageFormatDetector = serviceProvider.GetRequiredService<IImageFormatDetector>(); |
|||
var imageCompressorSelector = serviceProvider.GetRequiredService<IImageCompressorSelector>(); |
|||
var format = imageFormatDetector.FindFormat(stream); |
|||
return stream.CompressImageAsync(format, imageCompressorSelector.FindCompressor(format), cancellationToken); |
|||
} |
|||
|
|||
public static Stream ResizeImage(this Stream stream, IImageResizeParameter imageResizeParameter, IImageFormat imageFormat, IImageResizer imageResizer) |
|||
{ |
|||
if (!imageResizer.CanResize(imageFormat)) |
|||
{ |
|||
return stream; |
|||
} |
|||
|
|||
return imageResizer.Resize(stream, imageResizeParameter); |
|||
} |
|||
|
|||
public static Stream ResizeImage(this Stream stream, IImageResizeParameter imageResizeParameter, IServiceProvider serviceProvider) |
|||
{ |
|||
var imageFormatDetector = serviceProvider.GetRequiredService<IImageFormatDetector>(); |
|||
var imageResizerSelector = serviceProvider.GetRequiredService<IImageResizerSelector>(); |
|||
var format = imageFormatDetector.FindFormat(stream); |
|||
return stream.ResizeImage(imageResizeParameter, format, imageResizerSelector.FindResizer(format)); |
|||
} |
|||
|
|||
public static Task<Stream> ResizeImageAsync(this Stream stream, IImageResizeParameter imageResizeParameter, IImageFormat imageFormat, IImageResizer imageResizer, CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!imageResizer.CanResize(imageFormat)) |
|||
{ |
|||
return Task.FromResult(stream); |
|||
} |
|||
|
|||
return imageResizer.ResizeAsync(stream, imageResizeParameter, cancellationToken); |
|||
} |
|||
|
|||
public static Task<Stream> ResizeImageAsync(this Stream stream, IImageResizeParameter imageResizeParameter, IServiceProvider serviceProvider, CancellationToken cancellationToken = default) |
|||
{ |
|||
var imageFormatDetector = serviceProvider.GetRequiredService<IImageFormatDetector>(); |
|||
var imageResizerSelector = serviceProvider.GetRequiredService<IImageResizerSelector>(); |
|||
var format = imageFormatDetector.FindFormat(stream); |
|||
return stream.ResizeImageAsync(imageResizeParameter, format, imageResizerSelector.FindResizer(format), cancellationToken); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue