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