mirror of https://github.com/abpframework/abp.git
committed by
GitHub
75 changed files with 2698 additions and 12 deletions
@ -0,0 +1,307 @@ |
|||
# Image Manipulation |
|||
ABP Framework provides services to compress and resize images and implements these services with popular [ImageSharp](https://sixlabors.com/products/imagesharp/) and [Magick.NET](https://github.com/dlemstra/Magick.NET) libraries. You can use these services in your reusable modules, libraries and applications, so you don't depend on a specific imaging library. |
|||
|
|||
> The image resizer/compressor system is designed to be extensible. You can implement your own image resizer/compressor contributor and use it in your application. |
|||
|
|||
## Installation |
|||
|
|||
It is suggested to use the [ABP CLI](CLI.md) to install this package. |
|||
|
|||
### Using the ABP CLI |
|||
|
|||
Open a command line terminal in the folder of your project (.csproj file) and type the following command: |
|||
|
|||
```bash |
|||
abp add-package Volo.Abp.Imaging.Abstractions |
|||
``` |
|||
|
|||
### Manual Installation |
|||
|
|||
If you want to manually install; |
|||
|
|||
1. Add the [Volo.Abp.Imaging.Abstractions](https://www.nuget.org/packages/Volo.Abp.Imaging.Abstractions) NuGet package to your project: |
|||
|
|||
``` |
|||
Install-Package Volo.Abp.Imaging.Abstractions |
|||
``` |
|||
|
|||
2. Add the `AbpImagingAbstractionsModule` to the dependency list of your module: |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
//...other dependencies |
|||
typeof(AbpImagingAbstractionsModule) //Add the new module dependency |
|||
)] |
|||
public class YourModule : AbpModule |
|||
{ |
|||
} |
|||
``` |
|||
|
|||
## Providers |
|||
|
|||
ABP Framework provides two image resizer/compressor implementations out of the box: |
|||
|
|||
* [Magick.NET](#magicknet-provider) |
|||
* [ImageSharp](#imagesharp-provider) |
|||
|
|||
You should install one of these provides to make it actually working. |
|||
|
|||
> If none of the provider packages installed into your application, compress/resize operations return the untouched input image. |
|||
|
|||
## IImageResizer |
|||
|
|||
You can [inject](Dependency-Injection.md) the `IImageResizer` service and use it for image resize operations. Here is the available methods of the `IImageResizer` service: |
|||
|
|||
```csharp |
|||
public interface IImageResizer |
|||
{ |
|||
/* Works with a Stream object that represents an image */ |
|||
Task<ImageResizeResult<Stream>> ResizeAsync( |
|||
Stream stream, |
|||
ImageResizeArgs resizeArgs, |
|||
string mimeType = null, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
|
|||
/* Works with a byte array that contains an image file */ |
|||
Task<ImageResizeResult<byte[]>> ResizeAsync( |
|||
byte[] bytes, |
|||
ImageResizeArgs resizeArgs, |
|||
string mimeType = null, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
} |
|||
``` |
|||
|
|||
**Example usage:** |
|||
|
|||
```csharp |
|||
var result = await _imageResizer.ResizeAsync( |
|||
stream, /* A stream object that represents an image */ |
|||
new ImageResizeArgs |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
Mode = ImageResizeMode.Crop |
|||
}, |
|||
mimeType: "image/jpeg" |
|||
); |
|||
``` |
|||
|
|||
> You can use `MimeTypes.Image.Jpeg` constant instead of the `image/jpeg` magic string used in that example. |
|||
|
|||
### ImageResizeArgs |
|||
|
|||
The `ImageResizeArgs` is a class that is used to define the resize operation parameters. It has the following properties: |
|||
|
|||
* `Width`: The width of the resized image. |
|||
* `Height`: The height of the resized image. |
|||
* `Mode`: The resize mode (see the [ImageResizeMode](#imageresizemode) section for more information). |
|||
|
|||
### ImageResizeMode |
|||
|
|||
The `ImageResizeMode` is an enum that is used to define the resize mode. It has the following values: |
|||
|
|||
```csharp |
|||
public enum ImageResizeMode : byte |
|||
{ |
|||
None = 0, |
|||
Stretch = 1, |
|||
BoxPad = 2, |
|||
Min = 3, |
|||
Max = 4, |
|||
Crop = 5, |
|||
Pad = 6, |
|||
Default = 7 |
|||
} |
|||
``` |
|||
|
|||
### ImageResizeResult |
|||
|
|||
The `ImageResizeResult` is a generic class that is used to return the result of the image resize operations. It has the following properties: |
|||
|
|||
* `Result`: The resized image (stream or byte array). |
|||
* `State`: The result of the resize operation (type: `ImageProcessState`). |
|||
|
|||
### ImageProcessState |
|||
|
|||
The `ImageProcessState` is an enum that is used to return the the result of the image resize operations. It has the following values: |
|||
|
|||
```csharp |
|||
public enum ImageProcessState : byte |
|||
{ |
|||
Done = 1, |
|||
Canceled = 2, |
|||
Unsupported = 3, |
|||
} |
|||
``` |
|||
|
|||
### ImageResizeOptions |
|||
|
|||
`ImageResizeOptions` is an [options object](Options.md) that is used to configure the image resize system. It has the following properties: |
|||
|
|||
* `DefaultResizeMode`: The default resize mode. (Default: `ImageResizeMode.None`) |
|||
|
|||
## IImageCompressor |
|||
|
|||
You can [inject](Dependency-Injection.md) the `IImageCompressor` service and use it for image compression operations. Here is the available methods of the `IImageCompressor` service: |
|||
|
|||
```csharp |
|||
public interface IImageCompressor |
|||
{ |
|||
/* Works with a Stream object that represents an image */ |
|||
Task<ImageCompressResult<Stream>> CompressAsync( |
|||
Stream stream, |
|||
string mimeType = null, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
|
|||
/* Works with a byte array that contains an image file */ |
|||
Task<ImageCompressResult<byte[]>> CompressAsync( |
|||
byte[] bytes, |
|||
string mimeType = null, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
} |
|||
``` |
|||
|
|||
**Example usage:** |
|||
|
|||
```csharp |
|||
var result = await _imageCompressor.CompressAsync( |
|||
stream, /* A stream object that represents an image */ |
|||
mimeType: "image/jpeg" |
|||
); |
|||
``` |
|||
|
|||
### ImageCompressResult |
|||
|
|||
The `ImageCompressResult` is a generic class that is used to return the result of the image compression operations. It has the following properties: |
|||
|
|||
* `Result`: The compressed image (stream or byte array). |
|||
* `State`: The result of the compress operation (type: `ImageProcessState`). |
|||
|
|||
### ImageProcessState |
|||
|
|||
The `ImageProcessState` is an enum that is used to return the the result of the image compress operations. It has the following values: |
|||
|
|||
```csharp |
|||
public enum ImageProcessState : byte |
|||
{ |
|||
Done = 1, |
|||
Canceled = 2, |
|||
Unsupported = 3, |
|||
} |
|||
``` |
|||
|
|||
## Magick.NET Provider |
|||
|
|||
`Volo.Abp.Imaging.MagickNet` NuGet package implements the image operations using the [Magick.NET](https://github.com/dlemstra/Magick.NET) library. If you want to use that library as image process provider, add the `Volo.Abp.Imaging.MagickNet` NuGet package to your project, then add `AbpImagingMagickNetModule` to your [module](Module-Development-Basics.md)'s dependency list: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpImagingMagickNetModule))] |
|||
public class MyModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
### Configuration |
|||
|
|||
`MagickNetCompressOptions` is an [options object](Options.md) that is used to configure the Magick.NET image compression system. It has the following properties: |
|||
|
|||
* `OptimalCompression`: Indicates whether the optimal compression is enabled or not. (Default: `false`) |
|||
* `IgnoreUnsupportedFormats`: Indicates whether the unsupported formats are ignored or not. (Default: `false`) |
|||
* `Lossless`: Indicates whether the lossless compression is enabled or not. (Default: `false`) |
|||
|
|||
## ImageSharp Provider |
|||
|
|||
`Volo.Abp.Imaging.ImageSharp` NuGet package implements the image operations using the [ImageSharp](https://github.com/SixLabors/ImageSharp) library. If you want to use that library as image process provider, add the `Volo.Abp.Imaging.ImageSharp` NuGet package to your project, then add `AbpImagingImageSharpModule` to your [module](Module-Development-Basics.md)'s dependency list: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpImagingImageSharpModule))] |
|||
public class MyModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
### Configuration |
|||
|
|||
`ImageSharpCompressOptions` is an [options object](Options.md) that is used to configure the ImageSharp image compression system. It has the following properties: |
|||
|
|||
* `DefaultQuality`: The default quality of the JPEG and WebP encoders. (Default: `75`) |
|||
* [`JpegEncoder`](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder.html): The JPEG encoder. (Default: `JpegEncoder` with `Quality` set to `DefaultQuality`) |
|||
* [`PngEncoder`](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Formats.Png.PngEncoder.html): The PNG encoder. (Default: `PngEncoder` with `IgnoreMetadata` set to `true` and `CompressionLevel` set to `PngCompressionLevel.BestCompression`) |
|||
* [`WebPEncoder`](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.Formats.Webp.WebpEncoder.html): The WebP encoder. (Default: `WebPEncoder` with `Quality` set to `DefaultQuality`) |
|||
|
|||
**Example usage:** |
|||
|
|||
```csharp |
|||
Configure<ImageSharpCompressOptions>(options => |
|||
{ |
|||
options.JpegEncoder = new JpegEncoder |
|||
{ |
|||
Quality = 60 |
|||
}; |
|||
options.PngEncoder = new PngEncoder |
|||
{ |
|||
CompressionLevel = PngCompressionLevel.BestCompression |
|||
}; |
|||
options.WebPEncoder = new WebPEncoder |
|||
{ |
|||
Quality = 65 |
|||
}; |
|||
}); |
|||
``` |
|||
|
|||
## ASP.NET Core Integration |
|||
|
|||
`Volo.Abp.Imaging.AspNetCore` NuGet package defines attributes for controller actions that can automatically compress and/or resize uploaded files. |
|||
|
|||
To use the ASP.NET Core integration, add the `Volo.Abp.Imaging.AspNetCore` NuGet package to your project and add the `AbpImagingAspNetCoreModule` module to your module's dependency list: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpImagingAspNetCoreModule))] |
|||
public class MyModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
### CompressImageAttribute |
|||
|
|||
The `CompressImageAttribute` is used to compress the image before. `IFormFile`, `IRemoteStreamContent`, `Stream` and `IEnumrable<byte>` types are supported. It has the following properties: |
|||
|
|||
* `Parameters`: Names of the the parameters that are used to configure the image compression system. This is useful if your action has some non-image parameters. If you don't specify the parameters names, all of the method parameters are considered as image. |
|||
|
|||
**Example usage:** |
|||
|
|||
```csharp |
|||
[HttpPost] |
|||
[CompressImage] /* Compresses the given file (automatically determines the file mime type) */ |
|||
public async Task<IActionResult> Upload(IFormFile file) |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
### ResizeImageAttribute |
|||
|
|||
The `ResizeImageAttribute` is used to resize the image before requesting the action. `IFormFile`, `IRemoteStreamContent`, `Stream` and `IEnumrable<byte>` types are supported. It has the following properties: |
|||
|
|||
* `Parameters`: Names of the the parameters that are used to configure the image resize system. This is useful if your action has some non-image parameters. If you don't specify the parameters names, all of the method parameters are considered as image. |
|||
* `Width`: Target width of the resized image. |
|||
* `Height`: Target height of the resized image. |
|||
* `Mode`: The resize mode (see the [ImageResizeMode](#imageresizemode) section for more information). |
|||
|
|||
**Example usage:** |
|||
|
|||
```csharp |
|||
[HttpPost] |
|||
[ResizeImage(Width = 100, Height = 100, Mode = ImageResizeMode.Crop)] |
|||
public async Task<IActionResult> Upload(IFormFile file) |
|||
{ |
|||
//... |
|||
} |
|||
``` |
|||
@ -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,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -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.Imaging.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.Threading\Volo.Abp.Threading.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn(typeof(AbpThreadingModule))] |
|||
public class AbpImagingAbstractionsModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public interface IImageCompressor |
|||
{ |
|||
Task<ImageCompressResult<Stream>> CompressAsync( |
|||
Stream stream, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
|
|||
Task<ImageCompressResult<byte[]>> CompressAsync( |
|||
byte[] bytes, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public interface IImageCompressorContributor |
|||
{ |
|||
Task<ImageCompressResult<Stream>> TryCompressAsync( |
|||
Stream stream, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default); |
|||
Task<ImageCompressResult<byte[]>> TryCompressAsync( |
|||
byte[] bytes, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public interface IImageResizer |
|||
{ |
|||
Task<ImageResizeResult<Stream>> ResizeAsync( |
|||
Stream stream, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
|
|||
Task<ImageResizeResult<byte[]>> ResizeAsync( |
|||
byte[] bytes, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public interface IImageResizerContributor |
|||
{ |
|||
Task<ImageResizeResult<Stream>> TryResizeAsync( |
|||
Stream stream, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default); |
|||
|
|||
Task<ImageResizeResult<byte[]>> TryResizeAsync( |
|||
byte[] bytes, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageCompressResult<T> : ImageProcessResult<T> |
|||
{ |
|||
public ImageCompressResult(T result, ImageProcessState state) : base(result, state) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageCompressor : IImageCompressor, ITransientDependency |
|||
{ |
|||
protected IEnumerable<IImageCompressorContributor> ImageCompressorContributors { get; } |
|||
|
|||
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
|||
|
|||
public ImageCompressor(IEnumerable<IImageCompressorContributor> imageCompressorContributors, ICancellationTokenProvider cancellationTokenProvider) |
|||
{ |
|||
ImageCompressorContributors = imageCompressorContributors; |
|||
CancellationTokenProvider = cancellationTokenProvider; |
|||
} |
|||
|
|||
public virtual async Task<ImageCompressResult<Stream>> CompressAsync( |
|||
[NotNull] Stream stream, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
Check.NotNull(stream, nameof(stream)); |
|||
|
|||
foreach (var imageCompressorContributor in ImageCompressorContributors) |
|||
{ |
|||
var result = await imageCompressorContributor.TryCompressAsync(stream, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken)); |
|||
|
|||
if (result.State == ImageProcessState.Unsupported) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
public virtual async Task<ImageCompressResult<byte[]>> CompressAsync( |
|||
[NotNull] byte[] bytes, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
Check.NotNull(bytes, nameof(bytes)); |
|||
|
|||
foreach (var imageCompressorContributor in ImageCompressorContributors) |
|||
{ |
|||
var result = await imageCompressorContributor.TryCompressAsync(bytes, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken)); |
|||
|
|||
if (result.State == ImageProcessState.Unsupported) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
return new ImageCompressResult<byte[]>(bytes, ImageProcessState.Unsupported); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public abstract class ImageProcessResult<T> |
|||
{ |
|||
public T Result { get; } |
|||
public ImageProcessState State { get; } |
|||
|
|||
protected ImageProcessResult(T result, ImageProcessState state) |
|||
{ |
|||
Result = result; |
|||
State = state; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public enum ImageProcessState : byte |
|||
{ |
|||
Done = 1, |
|||
Canceled = 2, |
|||
Unsupported = 3, |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageResizeArgs |
|||
{ |
|||
private int _width; |
|||
public int Width |
|||
{ |
|||
get => _width; |
|||
set |
|||
{ |
|||
if (value < 0) |
|||
{ |
|||
throw new ArgumentException("Width cannot be negative!", nameof(value)); |
|||
} |
|||
|
|||
_width = value; |
|||
} |
|||
} |
|||
|
|||
private int _height; |
|||
public int Height |
|||
{ |
|||
get => _height; |
|||
set |
|||
{ |
|||
if (value < 0) |
|||
{ |
|||
throw new ArgumentException("Height cannot be negative!", nameof(value)); |
|||
} |
|||
|
|||
_height = value; |
|||
} |
|||
} |
|||
|
|||
public ImageResizeMode Mode { get; set; } = ImageResizeMode.Default; |
|||
|
|||
public ImageResizeArgs(int? width = null, int? height = null, ImageResizeMode? mode = null) |
|||
{ |
|||
if (mode.HasValue) |
|||
{ |
|||
Mode = mode.Value; |
|||
} |
|||
|
|||
Width = width ?? 0; |
|||
Height = height ?? 0; |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public enum ImageResizeMode : byte |
|||
{ |
|||
None = 0, |
|||
Stretch = 1, |
|||
BoxPad = 2, |
|||
Min = 3, |
|||
Max = 4, |
|||
Crop = 5, |
|||
Pad = 6, |
|||
Default = 7 |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageResizeOptions |
|||
{ |
|||
public ImageResizeMode DefaultResizeMode { get; set; } = ImageResizeMode.None; |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageResizeResult<T> : ImageProcessResult<T> |
|||
{ |
|||
public ImageResizeResult(T result, ImageProcessState state) : base(result, state) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageResizer : IImageResizer, ITransientDependency |
|||
{ |
|||
protected IEnumerable<IImageResizerContributor> ImageResizerContributors { get; } |
|||
|
|||
protected ImageResizeOptions ImageResizeOptions { get; } |
|||
|
|||
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
|||
|
|||
public ImageResizer( |
|||
IEnumerable<IImageResizerContributor> imageResizerContributors, |
|||
IOptions<ImageResizeOptions> imageResizeOptions, |
|||
ICancellationTokenProvider cancellationTokenProvider) |
|||
{ |
|||
ImageResizerContributors = imageResizerContributors; |
|||
CancellationTokenProvider = cancellationTokenProvider; |
|||
ImageResizeOptions = imageResizeOptions.Value; |
|||
} |
|||
|
|||
public virtual async Task<ImageResizeResult<Stream>> ResizeAsync( |
|||
[NotNull] Stream stream, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
Check.NotNull(stream, nameof(stream)); |
|||
|
|||
ChangeDefaultResizeMode(resizeArgs); |
|||
|
|||
foreach (var imageResizerContributor in ImageResizerContributors) |
|||
{ |
|||
var result = await imageResizerContributor.TryResizeAsync(stream, resizeArgs, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken)); |
|||
|
|||
if (result.State == ImageProcessState.Unsupported) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
public virtual async Task<ImageResizeResult<byte[]>> ResizeAsync( |
|||
[NotNull] byte[] bytes, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
Check.NotNull(bytes, nameof(bytes)); |
|||
|
|||
ChangeDefaultResizeMode(resizeArgs); |
|||
|
|||
foreach (var imageResizerContributor in ImageResizerContributors) |
|||
{ |
|||
var result = await imageResizerContributor.TryResizeAsync(bytes, resizeArgs, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken)); |
|||
|
|||
if (result.State == ImageProcessState.Unsupported) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
return new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
protected virtual void ChangeDefaultResizeMode(ImageResizeArgs resizeArgs) |
|||
{ |
|||
if (resizeArgs.Mode == ImageResizeMode.Default) |
|||
{ |
|||
resizeArgs.Mode = ImageResizeOptions.DefaultResizeMode; |
|||
} |
|||
} |
|||
} |
|||
@ -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,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<PackageId>Volo.Abp.Imaging.AspNetCore</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.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn(typeof(AbpImagingAbstractionsModule))] |
|||
public class AbpImagingAspNetCoreModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Content; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class CompressImageAttribute : ActionFilterAttribute |
|||
{ |
|||
public string[] Parameters { get; } |
|||
|
|||
public CompressImageAttribute(params string[] parameters) |
|||
{ |
|||
Parameters = parameters; |
|||
} |
|||
|
|||
public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) |
|||
{ |
|||
var parameters = Parameters.Any() |
|||
? context.ActionArguments.Where(x => Parameters.Contains(x.Key)).ToArray() |
|||
: context.ActionArguments.ToArray(); |
|||
|
|||
var imageCompressor = context.HttpContext.RequestServices.GetRequiredService<IImageCompressor>(); |
|||
|
|||
foreach (var (key, value) in parameters) |
|||
{ |
|||
object compressedValue = value switch { |
|||
IFormFile file => await CompressImageAsync(file, imageCompressor), |
|||
IRemoteStreamContent remoteStreamContent => await CompressImageAsync(remoteStreamContent, imageCompressor), |
|||
Stream stream => await CompressImageAsync(stream, imageCompressor), |
|||
IEnumerable<byte> bytes => await CompressImageAsync(bytes.ToArray(), imageCompressor), |
|||
_ => null |
|||
}; |
|||
|
|||
if (compressedValue != null) |
|||
{ |
|||
context.ActionArguments[key] = compressedValue; |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
} |
|||
|
|||
protected virtual async Task<IFormFile> CompressImageAsync(IFormFile file, IImageCompressor imageCompressor) |
|||
{ |
|||
if(file.Headers == null || file.ContentType == null || !file.ContentType.StartsWith("image/")) |
|||
{ |
|||
return file; |
|||
} |
|||
|
|||
var result = await imageCompressor.CompressAsync(file.OpenReadStream(), file.ContentType); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return file; |
|||
} |
|||
|
|||
return new FormFile(result.Result, 0, result.Result.Length, file.Name, file.FileName) { |
|||
Headers = file.Headers, |
|||
}; |
|||
} |
|||
|
|||
protected virtual async Task<IRemoteStreamContent> CompressImageAsync(IRemoteStreamContent remoteStreamContent, IImageCompressor imageCompressor) |
|||
{ |
|||
if(remoteStreamContent.ContentType == null || !remoteStreamContent.ContentType.StartsWith("image/")) |
|||
{ |
|||
return remoteStreamContent; |
|||
} |
|||
|
|||
var result = await imageCompressor.CompressAsync(remoteStreamContent.GetStream(), remoteStreamContent.ContentType); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return remoteStreamContent; |
|||
} |
|||
|
|||
var fileName = remoteStreamContent.FileName; |
|||
var contentType = remoteStreamContent.ContentType; |
|||
remoteStreamContent.Dispose(); |
|||
return new RemoteStreamContent(result.Result, fileName, contentType); |
|||
} |
|||
|
|||
protected virtual async Task<Stream> CompressImageAsync(Stream stream, IImageCompressor imageCompressor) |
|||
{ |
|||
var result = await imageCompressor.CompressAsync(stream); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return stream; |
|||
} |
|||
|
|||
await stream.DisposeAsync(); |
|||
return result.Result; |
|||
} |
|||
|
|||
protected virtual async Task<byte[]> CompressImageAsync(byte[] bytes, IImageCompressor imageCompressor) |
|||
{ |
|||
return (await imageCompressor.CompressAsync(bytes)).Result; |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Content; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ResizeImageAttribute : ActionFilterAttribute |
|||
{ |
|||
public int? Width { get; } |
|||
public int? Height { get; } |
|||
|
|||
public ImageResizeMode Mode { get; set; } |
|||
public string[] Parameters { get; } |
|||
|
|||
public ResizeImageAttribute(int width, int height, params string[] parameters) |
|||
{ |
|||
Width = width; |
|||
Height = height; |
|||
Parameters = parameters; |
|||
} |
|||
|
|||
public ResizeImageAttribute(int size, params string[] parameters) |
|||
{ |
|||
Width = size; |
|||
Height = size; |
|||
Parameters = parameters; |
|||
} |
|||
|
|||
public async override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) |
|||
{ |
|||
var parameters = Parameters.Any() |
|||
? context.ActionArguments.Where(x => Parameters.Contains(x.Key)).ToArray() |
|||
: context.ActionArguments.ToArray(); |
|||
|
|||
var imageResizer = context.HttpContext.RequestServices.GetRequiredService<IImageResizer>(); |
|||
|
|||
foreach (var (key, value) in parameters) |
|||
{ |
|||
object resizedValue = value switch { |
|||
IFormFile file => await ResizeImageAsync(file, imageResizer), |
|||
IRemoteStreamContent remoteStreamContent => await ResizeImageAsync(remoteStreamContent, imageResizer), |
|||
Stream stream => await ResizeImageAsync(stream, imageResizer), |
|||
IEnumerable<byte> bytes => await ResizeImageAsync(bytes.ToArray(), imageResizer), |
|||
_ => null |
|||
}; |
|||
|
|||
if (resizedValue != null) |
|||
{ |
|||
context.ActionArguments[key] = resizedValue; |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
} |
|||
|
|||
protected virtual async Task<IFormFile> ResizeImageAsync(IFormFile file, IImageResizer imageResizer) |
|||
{ |
|||
if(file.Headers == null || file.ContentType == null || !file.ContentType.StartsWith("image/")) |
|||
{ |
|||
return file; |
|||
} |
|||
|
|||
var result = await imageResizer.ResizeAsync(file.OpenReadStream(), new ImageResizeArgs(Width, Height, Mode), file.ContentType); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return file; |
|||
} |
|||
|
|||
return new FormFile(result.Result, 0, result.Result.Length, file.Name, file.FileName) { |
|||
Headers = file.Headers |
|||
}; |
|||
} |
|||
|
|||
protected virtual async Task<IRemoteStreamContent> ResizeImageAsync(IRemoteStreamContent remoteStreamContent, IImageResizer imageResizer) |
|||
{ |
|||
if(remoteStreamContent.ContentType == null || !remoteStreamContent.ContentType.StartsWith("image/")) |
|||
{ |
|||
return remoteStreamContent; |
|||
} |
|||
|
|||
var result = await imageResizer.ResizeAsync(remoteStreamContent.GetStream(), new ImageResizeArgs(Width, Height, Mode), remoteStreamContent.ContentType); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return remoteStreamContent; |
|||
} |
|||
|
|||
var fileName = remoteStreamContent.FileName; |
|||
var contentType = remoteStreamContent.ContentType; |
|||
remoteStreamContent.Dispose(); |
|||
return new RemoteStreamContent(result.Result, fileName, contentType); |
|||
} |
|||
|
|||
protected virtual async Task<Stream> ResizeImageAsync(Stream stream, IImageResizer imageResizer) |
|||
{ |
|||
var result = await imageResizer.ResizeAsync(stream, new ImageResizeArgs(Width, Height, Mode)); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return stream; |
|||
} |
|||
|
|||
await stream.DisposeAsync(); |
|||
return result.Result; |
|||
} |
|||
|
|||
protected virtual async Task<byte[]> ResizeImageAsync(byte[] bytes, IImageResizer imageResizer) |
|||
{ |
|||
return (await imageResizer.ResizeAsync(bytes, new ImageResizeArgs(Width, Height, Mode))).Result; |
|||
} |
|||
} |
|||
@ -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.Imaging.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.Imaging.Abstractions\Volo.Abp.Imaging.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.Imaging; |
|||
|
|||
[DependsOn(typeof(AbpImagingAbstractionsModule))] |
|||
public class AbpImagingImageSharpModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.Formats.Png; |
|||
using SixLabors.ImageSharp.Formats.Webp; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageSharpCompressOptions |
|||
{ |
|||
public IImageEncoder JpegEncoder { get; set; } |
|||
|
|||
public IImageEncoder PngEncoder { get; set; } |
|||
|
|||
public IImageEncoder WebpEncoder { get; set; } |
|||
|
|||
public int DefaultQuality { get; set; } = 75; |
|||
public ImageSharpCompressOptions() |
|||
{ |
|||
JpegEncoder = new JpegEncoder { |
|||
Quality = DefaultQuality |
|||
}; |
|||
|
|||
PngEncoder = new PngEncoder { |
|||
CompressionLevel = PngCompressionLevel.BestCompression, |
|||
IgnoreMetadata = true |
|||
}; |
|||
|
|||
WebpEncoder = new WebpEncoder { |
|||
Quality = DefaultQuality |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Options; |
|||
using SixLabors.ImageSharp; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.Formats.Png; |
|||
using SixLabors.ImageSharp.Formats.Webp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageSharpImageCompressorContributor : IImageCompressorContributor, ITransientDependency |
|||
{ |
|||
protected ImageSharpCompressOptions Options { get; } |
|||
|
|||
public ImageSharpImageCompressorContributor(IOptions<ImageSharpCompressOptions> options) |
|||
{ |
|||
Options = options.Value; |
|||
} |
|||
|
|||
public virtual async Task<ImageCompressResult<Stream>> TryCompressAsync( |
|||
Stream stream, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) |
|||
{ |
|||
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
var (image, format) = await Image.LoadWithFormatAsync(stream, cancellationToken); |
|||
|
|||
if (!CanCompress(format.DefaultMimeType)) |
|||
{ |
|||
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
var memoryStream = await GetStreamFromImageAsync(image, format, cancellationToken); |
|||
|
|||
if (memoryStream.Length < stream.Length) |
|||
{ |
|||
return new ImageCompressResult<Stream>(memoryStream, ImageProcessState.Done); |
|||
} |
|||
|
|||
memoryStream.Dispose(); |
|||
return new ImageCompressResult<Stream>(stream, ImageProcessState.Canceled); |
|||
} |
|||
|
|||
public virtual async Task<ImageCompressResult<byte[]>> TryCompressAsync( |
|||
byte[] bytes, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) |
|||
{ |
|||
return new ImageCompressResult<byte[]>(bytes, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
using var ms = new MemoryStream(bytes); |
|||
var result = await TryCompressAsync(ms, mimeType, cancellationToken); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return new ImageCompressResult<byte[]>(bytes, result.State); |
|||
} |
|||
|
|||
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); |
|||
result.Result.Dispose(); |
|||
return new ImageCompressResult<byte[]>(newBytes, result.State); |
|||
} |
|||
|
|||
protected virtual bool CanCompress(string mimeType) |
|||
{ |
|||
return mimeType switch { |
|||
MimeTypes.Image.Jpeg => true, |
|||
MimeTypes.Image.Png => true, |
|||
MimeTypes.Image.Webp => true, |
|||
_ => false |
|||
}; |
|||
} |
|||
|
|||
protected virtual async Task<Stream> GetStreamFromImageAsync( |
|||
Image image, |
|||
IImageFormat format, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var memoryStream = new MemoryStream(); |
|||
|
|||
try |
|||
{ |
|||
await image.SaveAsync(memoryStream, GetEncoder(format), cancellationToken: cancellationToken); |
|||
|
|||
memoryStream.Position = 0; |
|||
|
|||
return memoryStream; |
|||
} |
|||
catch |
|||
{ |
|||
memoryStream.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
protected virtual IImageEncoder GetEncoder(IImageFormat format) |
|||
{ |
|||
switch (format.DefaultMimeType) |
|||
{ |
|||
case MimeTypes.Image.Jpeg: |
|||
return Options.JpegEncoder ?? new JpegEncoder(); |
|||
case MimeTypes.Image.Png: |
|||
return Options.PngEncoder ?? new PngEncoder(); |
|||
case MimeTypes.Image.Webp: |
|||
return Options.WebpEncoder ?? new WebpEncoder(); |
|||
default: |
|||
throw new NotSupportedException($"No encoder available for the given format: {format.Name}"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using SixLabors.ImageSharp; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageSharpImageResizerContributor : IImageResizerContributor, ITransientDependency |
|||
{ |
|||
public virtual async Task<ImageResizeResult<Stream>> TryResizeAsync( |
|||
Stream stream, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) |
|||
{ |
|||
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
var (image, format) = await Image.LoadWithFormatAsync(stream, cancellationToken); |
|||
|
|||
if (!CanResize(format.DefaultMimeType)) |
|||
{ |
|||
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
if (ResizeModeMap.TryGetValue(resizeArgs.Mode, out var resizeMode)) |
|||
{ |
|||
image.Mutate(x => x.Resize(new ResizeOptions { Size = GetSize(resizeArgs), Mode = resizeMode })); |
|||
} |
|||
else |
|||
{ |
|||
throw new NotSupportedException("Resize mode " + resizeArgs.Mode + "is not supported!"); |
|||
} |
|||
|
|||
var memoryStream = new MemoryStream(); |
|||
|
|||
try |
|||
{ |
|||
await image.SaveAsync(memoryStream, format, cancellationToken: cancellationToken); |
|||
memoryStream.Position = 0; |
|||
return new ImageResizeResult<Stream>(memoryStream, ImageProcessState.Done); |
|||
} |
|||
catch |
|||
{ |
|||
memoryStream.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
public virtual async Task<ImageResizeResult<byte[]>> TryResizeAsync( |
|||
byte[] bytes, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(mimeType) && !CanResize(mimeType)) |
|||
{ |
|||
return new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
using var ms = new MemoryStream(bytes); |
|||
|
|||
var result = await TryResizeAsync(ms, resizeArgs, mimeType, cancellationToken); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return new ImageResizeResult<byte[]>(bytes, result.State); |
|||
} |
|||
|
|||
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); |
|||
|
|||
result.Result.Dispose(); |
|||
|
|||
return new ImageResizeResult<byte[]>(newBytes, result.State); |
|||
} |
|||
|
|||
protected virtual bool CanResize(string mimeType) |
|||
{ |
|||
return mimeType switch { |
|||
MimeTypes.Image.Jpeg => true, |
|||
MimeTypes.Image.Png => true, |
|||
MimeTypes.Image.Gif => true, |
|||
MimeTypes.Image.Bmp => true, |
|||
MimeTypes.Image.Tiff => true, |
|||
MimeTypes.Image.Webp => true, |
|||
_ => false |
|||
}; |
|||
} |
|||
|
|||
protected Dictionary<ImageResizeMode, ResizeMode> ResizeModeMap = new() { |
|||
{ ImageResizeMode.None, default }, |
|||
{ ImageResizeMode.Stretch, ResizeMode.Stretch }, |
|||
{ ImageResizeMode.BoxPad, ResizeMode.BoxPad }, |
|||
{ ImageResizeMode.Min, ResizeMode.Min }, |
|||
{ ImageResizeMode.Max, ResizeMode.Max }, |
|||
{ ImageResizeMode.Crop, ResizeMode.Crop }, |
|||
{ ImageResizeMode.Pad, ResizeMode.Pad } |
|||
}; |
|||
|
|||
private static Size GetSize(ImageResizeArgs resizeArgs) |
|||
{ |
|||
var size = new Size(); |
|||
|
|||
if (resizeArgs.Width > 0) |
|||
{ |
|||
size.Width = resizeArgs.Width; |
|||
} |
|||
|
|||
if (resizeArgs.Height > 0) |
|||
{ |
|||
size.Height = resizeArgs.Height; |
|||
} |
|||
|
|||
return size; |
|||
} |
|||
} |
|||
@ -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,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -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.Imaging.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.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.1.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn(typeof(AbpImagingAbstractionsModule))] |
|||
public class AbpImagingMagickNetModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using ImageMagick; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class MagickImageCompressorContributor : IImageCompressorContributor, ITransientDependency |
|||
{ |
|||
protected MagickNetCompressOptions Options { get; } |
|||
|
|||
protected readonly ImageOptimizer Optimizer; |
|||
|
|||
public MagickImageCompressorContributor(IOptions<MagickNetCompressOptions> options) |
|||
{ |
|||
Options = options.Value; |
|||
Optimizer = new ImageOptimizer |
|||
{ |
|||
OptimalCompression = Options.OptimalCompression, |
|||
IgnoreUnsupportedFormats = Options.IgnoreUnsupportedFormats |
|||
}; |
|||
} |
|||
|
|||
public virtual async Task<ImageCompressResult<Stream>> TryCompressAsync( |
|||
Stream stream, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) |
|||
{ |
|||
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
var memoryStream = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken); |
|||
|
|||
try |
|||
{ |
|||
if (!Optimizer.IsSupported(memoryStream)) |
|||
{ |
|||
return new ImageCompressResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
if (Compress(memoryStream)) |
|||
{ |
|||
return new ImageCompressResult<Stream>(memoryStream, ImageProcessState.Done); |
|||
} |
|||
|
|||
memoryStream.Dispose(); |
|||
|
|||
return new ImageCompressResult<Stream>(stream, ImageProcessState.Canceled); |
|||
} |
|||
catch |
|||
{ |
|||
memoryStream.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
public virtual async Task<ImageCompressResult<byte[]>> TryCompressAsync( |
|||
byte[] bytes, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) |
|||
{ |
|||
return new ImageCompressResult<byte[]>(bytes, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
using var memoryStream = new MemoryStream(bytes); |
|||
var result = await TryCompressAsync(memoryStream, mimeType, cancellationToken); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return new ImageCompressResult<byte[]>(bytes, result.State); |
|||
} |
|||
|
|||
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); |
|||
|
|||
result.Result.Dispose(); |
|||
|
|||
return new ImageCompressResult<byte[]>(newBytes, result.State); |
|||
} |
|||
|
|||
protected virtual bool CanCompress(string mimeType) |
|||
{ |
|||
return mimeType switch { |
|||
MimeTypes.Image.Jpeg => true, |
|||
MimeTypes.Image.Png => true, |
|||
MimeTypes.Image.Gif => true, |
|||
_ => false |
|||
}; |
|||
} |
|||
|
|||
protected virtual bool Compress(Stream stream) |
|||
{ |
|||
return Options.Lossless ? Optimizer.LosslessCompress(stream) : Optimizer.Compress(stream); |
|||
} |
|||
} |
|||
@ -0,0 +1,325 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using ImageMagick; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class MagickImageResizerContributor : IImageResizerContributor, ITransientDependency |
|||
{ |
|||
private const int Min = 1; |
|||
|
|||
public virtual async Task<ImageResizeResult<Stream>> TryResizeAsync( |
|||
Stream stream, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType)) |
|||
{ |
|||
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
var memoryStream = await stream.CreateMemoryStreamAsync(cancellationToken: cancellationToken); |
|||
|
|||
try |
|||
{ |
|||
using var image = new MagickImage(memoryStream); |
|||
|
|||
if (mimeType.IsNullOrWhiteSpace() && !CanResize(image.FormatInfo?.MimeType)) |
|||
{ |
|||
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
Resize(image, resizeArgs); |
|||
|
|||
memoryStream.Position = 0; |
|||
await image.WriteAsync(memoryStream, cancellationToken); |
|||
memoryStream.SetLength(memoryStream.Position); |
|||
memoryStream.Position = 0; |
|||
|
|||
return new ImageResizeResult<Stream>(memoryStream, ImageProcessState.Done); |
|||
} |
|||
catch |
|||
{ |
|||
memoryStream.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
public virtual Task<ImageResizeResult<byte[]>> TryResizeAsync( |
|||
byte[] bytes, |
|||
ImageResizeArgs resizeArgs, |
|||
[CanBeNull] string mimeType = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType)) |
|||
{ |
|||
return Task.FromResult(new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported)); |
|||
} |
|||
|
|||
using var image = new MagickImage(bytes); |
|||
|
|||
if (mimeType.IsNullOrWhiteSpace() && !CanResize(image.FormatInfo?.MimeType)) |
|||
{ |
|||
return Task.FromResult(new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported)); |
|||
} |
|||
|
|||
Resize(image, resizeArgs); |
|||
|
|||
return Task.FromResult(new ImageResizeResult<byte[]>(image.ToByteArray(), ImageProcessState.Done)); |
|||
} |
|||
|
|||
protected virtual bool CanResize(string mimeType) |
|||
{ |
|||
return mimeType switch { |
|||
MimeTypes.Image.Jpeg => true, |
|||
MimeTypes.Image.Png => true, |
|||
MimeTypes.Image.Gif => true, |
|||
MimeTypes.Image.Bmp => true, |
|||
MimeTypes.Image.Tiff => true, |
|||
MimeTypes.Image.Webp => true, |
|||
_ => false |
|||
}; |
|||
} |
|||
|
|||
protected virtual void Resize(MagickImage image, ImageResizeArgs resizeArgs) |
|||
{ |
|||
ApplyResizeMode(image, resizeArgs); |
|||
} |
|||
|
|||
protected virtual void ApplyResizeMode(MagickImage image, ImageResizeArgs resizeArgs) |
|||
{ |
|||
switch (resizeArgs.Mode) |
|||
{ |
|||
case ImageResizeMode.None: |
|||
ResizeModeNone(image, resizeArgs); |
|||
break; |
|||
case ImageResizeMode.Stretch: |
|||
ResizeStretch(image, resizeArgs); |
|||
break; |
|||
case ImageResizeMode.Pad: |
|||
ResizePad(image, resizeArgs); |
|||
break; |
|||
case ImageResizeMode.BoxPad: |
|||
ResizeBoxPad(image, resizeArgs); |
|||
break; |
|||
case ImageResizeMode.Max: |
|||
ResizeMax(image, resizeArgs); |
|||
break; |
|||
case ImageResizeMode.Min: |
|||
ResizeMin(image, resizeArgs); |
|||
break; |
|||
case ImageResizeMode.Crop: |
|||
ResizeCrop(image, resizeArgs); |
|||
break; |
|||
default: |
|||
throw new NotSupportedException("Resize mode " + resizeArgs.Mode + "is not supported!"); |
|||
} |
|||
} |
|||
|
|||
|
|||
protected virtual int GetTargetHeight(ImageResizeArgs resizeArgs, int min, int sourceWidth, int sourceHeight) |
|||
{ |
|||
if (resizeArgs.Height == 0 && resizeArgs.Width > 0) |
|||
{ |
|||
return Math.Max(min, (int)Math.Round(sourceHeight * resizeArgs.Width / (float)sourceWidth)); |
|||
} |
|||
|
|||
return resizeArgs.Height; |
|||
} |
|||
|
|||
protected virtual int GetTargetWidth(ImageResizeArgs resizeArgs, int min, int sourceWidth, int sourceHeight) |
|||
{ |
|||
if (resizeArgs.Width == 0 && resizeArgs.Height > 0) |
|||
{ |
|||
return Math.Max(min, (int)Math.Round(sourceWidth * resizeArgs.Height / (float)sourceHeight)); |
|||
} |
|||
|
|||
return resizeArgs.Width; |
|||
} |
|||
|
|||
protected virtual void ResizeModeNone(IMagickImage image, ImageResizeArgs resizeArgs) |
|||
{ |
|||
var sourceWidth = image.Width; |
|||
var sourceHeight = image.Height; |
|||
|
|||
image.Resize( |
|||
GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight), |
|||
GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight) |
|||
); |
|||
} |
|||
|
|||
protected virtual void ResizeStretch(IMagickImage image, ImageResizeArgs resizeArgs) |
|||
{ |
|||
var sourceWidth = image.Width; |
|||
var sourceHeight = image.Height; |
|||
|
|||
image.Resize( |
|||
new MagickGeometry( |
|||
GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight), |
|||
GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight)) { IgnoreAspectRatio = true }); |
|||
} |
|||
|
|||
protected virtual void ResizePad(MagickImage image, ImageResizeArgs resizeArgs) |
|||
{ |
|||
var sourceWidth = image.Width; |
|||
var sourceHeight = image.Height; |
|||
|
|||
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
|
|||
var percentHeight = CalculatePercent(sourceHeight, targetHeight); |
|||
var percentWidth = CalculatePercent(sourceWidth, targetWidth); |
|||
|
|||
var newWidth = targetWidth; |
|||
var newHeight = targetHeight; |
|||
|
|||
if (percentHeight < percentWidth) |
|||
{ |
|||
newWidth = (int)Math.Round(sourceWidth * percentHeight); |
|||
} |
|||
else |
|||
{ |
|||
newHeight = (int)Math.Round(sourceHeight * percentWidth); |
|||
} |
|||
|
|||
image.Resize(newWidth, newHeight); |
|||
image.Extent(targetWidth, targetHeight, Gravity.Center, MagickColors.Transparent); |
|||
} |
|||
|
|||
protected virtual void ResizeBoxPad(MagickImage image, ImageResizeArgs resizeArgs) |
|||
{ |
|||
var sourceWidth = image.Width; |
|||
var sourceHeight = image.Height; |
|||
|
|||
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
|
|||
var percentHeight = CalculatePercent(sourceHeight, targetHeight); |
|||
var percentWidth = CalculatePercent(sourceWidth, targetWidth); |
|||
|
|||
var newWidth = targetWidth; |
|||
var newHeight = targetHeight; |
|||
|
|||
var boxPadWidth = targetWidth > 0 ? targetWidth : (int)Math.Round(sourceWidth * percentHeight); |
|||
var boxPadHeight = targetHeight > 0 ? targetHeight : (int)Math.Round(sourceHeight * percentWidth); |
|||
|
|||
if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) |
|||
{ |
|||
newWidth = boxPadWidth; |
|||
newHeight = boxPadHeight; |
|||
} |
|||
|
|||
image.Resize(newWidth, newHeight); |
|||
image.Extent(targetWidth, targetHeight, Gravity.Center, MagickColors.Transparent); |
|||
} |
|||
|
|||
protected virtual void ResizeMax(IMagickImage image, ImageResizeArgs resizeArgs) |
|||
{ |
|||
var sourceWidth = image.Width; |
|||
var sourceHeight = image.Height; |
|||
|
|||
var imageRatio = CalculateRatio(sourceWidth, sourceHeight); |
|||
|
|||
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
|
|||
var ratio = CalculateRatio(targetWidth, targetHeight); |
|||
|
|||
var percentHeight = CalculatePercent(sourceHeight, targetHeight); |
|||
var percentWidth = CalculatePercent(sourceWidth, targetWidth); |
|||
|
|||
if (imageRatio < ratio) |
|||
{ |
|||
targetHeight = (int)(sourceHeight * percentWidth); |
|||
} |
|||
else |
|||
{ |
|||
targetWidth = (int)(sourceWidth * percentHeight); |
|||
} |
|||
|
|||
image.Resize(targetWidth, targetHeight); |
|||
} |
|||
|
|||
protected virtual void ResizeMin(MagickImage image, ImageResizeArgs resizeArgs) |
|||
{ |
|||
var sourceWidth = image.Width; |
|||
var sourceHeight = image.Height; |
|||
|
|||
var imageRatio = CalculateRatio(sourceWidth, sourceHeight); |
|||
|
|||
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
|
|||
var percentWidth = CalculatePercent(sourceWidth, targetWidth); |
|||
|
|||
if (targetWidth > sourceWidth || targetHeight > sourceHeight) |
|||
{ |
|||
targetWidth = sourceWidth; |
|||
targetHeight = sourceHeight; |
|||
} |
|||
else |
|||
{ |
|||
var widthDiff = sourceWidth - targetWidth; |
|||
var heightDiff = sourceHeight - targetHeight; |
|||
|
|||
if (widthDiff > heightDiff) |
|||
{ |
|||
targetWidth = (int)Math.Round(targetHeight / imageRatio); |
|||
} |
|||
else if (widthDiff < heightDiff) |
|||
{ |
|||
targetHeight = (int)Math.Round(targetWidth * imageRatio); |
|||
} |
|||
else |
|||
{ |
|||
if (targetHeight > targetWidth) |
|||
{ |
|||
targetWidth = (int)Math.Round(sourceHeight * percentWidth); |
|||
} |
|||
else |
|||
{ |
|||
targetHeight = (int)Math.Round(sourceHeight * percentWidth); |
|||
} |
|||
} |
|||
} |
|||
|
|||
image.Resize(targetWidth, targetHeight); |
|||
} |
|||
|
|||
protected virtual void ResizeCrop(MagickImage image, ImageResizeArgs resizeArgs) |
|||
{ |
|||
var sourceWidth = image.Width; |
|||
var sourceHeight = image.Height; |
|||
|
|||
var targetWidth = GetTargetWidth(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
var targetHeight = GetTargetHeight(resizeArgs, Min, sourceWidth, sourceHeight); |
|||
|
|||
image.Extent( |
|||
targetWidth, |
|||
targetHeight, |
|||
Gravity.Center, |
|||
MagickColors.Transparent); |
|||
|
|||
image.Crop( |
|||
new MagickGeometry( |
|||
targetWidth, |
|||
targetHeight) { IgnoreAspectRatio = true }, |
|||
Gravity.Center); |
|||
} |
|||
|
|||
protected virtual float CalculatePercent(int imageHeightOrWidth, int heightOrWidth) |
|||
{ |
|||
return heightOrWidth / (float)imageHeightOrWidth; |
|||
} |
|||
|
|||
protected virtual float CalculateRatio(int width, int height) |
|||
{ |
|||
return height / (float)width; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class MagickNetCompressOptions |
|||
{ |
|||
public bool OptimalCompression { get; set; } |
|||
public bool IgnoreUnsupportedFormats { get; set; } |
|||
public bool Lossless { get; set; } |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.test" |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj" /> |
|||
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Volo\Abp\Imaging\Files\**" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,11 @@ |
|||
using Volo.Abp.Testing; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public abstract class AbpImagingAbstractionsTestBase : AbpIntegratedTest<AbpImagingAbstractionsTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpImagingAbstractionsModule), |
|||
typeof(AbpTestBaseModule) |
|||
)] |
|||
public class AbpImagingAbstractionsTestModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 464 KiB |
|
After Width: | Height: | Size: 66 KiB |
@ -0,0 +1,51 @@ |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class IImageCompressor_Tests : AbpImagingAbstractionsTestBase |
|||
{ |
|||
protected IImageCompressor ImageCompressor { get; } |
|||
|
|||
public IImageCompressor_Tests() |
|||
{ |
|||
ImageCompressor = GetRequiredService<IImageCompressor>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Jpg() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var compressedImage = await ImageCompressor.CompressAsync(jpegImage); |
|||
|
|||
compressedImage.ShouldNotBeNull(); |
|||
compressedImage.State.ShouldBe(ImageProcessState.Unsupported); |
|||
|
|||
compressedImage.Result.ShouldBe(jpegImage); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Png() |
|||
{ |
|||
await using var pngImage = ImageFileHelper.GetPngTestFileStream(); |
|||
var compressedImage = await ImageCompressor.CompressAsync(pngImage); |
|||
|
|||
compressedImage.ShouldNotBeNull(); |
|||
compressedImage.State.ShouldBe(ImageProcessState.Unsupported); |
|||
|
|||
compressedImage.Result.ShouldBe(pngImage); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Webp() |
|||
{ |
|||
await using var webpImage = ImageFileHelper.GetWebpTestFileStream(); |
|||
var compressedImage = await ImageCompressor.CompressAsync(webpImage); |
|||
|
|||
compressedImage.ShouldNotBeNull(); |
|||
compressedImage.State.ShouldBe(ImageProcessState.Unsupported); |
|||
|
|||
compressedImage.Result.ShouldBe(webpImage); |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class IImageResizer_Tests : AbpImagingAbstractionsTestBase |
|||
{ |
|||
protected IImageResizer ImageResizer { get; } |
|||
|
|||
public IImageResizer_Tests() |
|||
{ |
|||
ImageResizer = GetRequiredService<IImageResizer>(); |
|||
} |
|||
[Fact] |
|||
public async Task Should_Resize_Jpg() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Unsupported); |
|||
|
|||
resizedImage.Result.ShouldBe(jpegImage); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Png() |
|||
{ |
|||
await using var pngImage = ImageFileHelper.GetPngTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(pngImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Unsupported); |
|||
|
|||
resizedImage.Result.ShouldBe(pngImage); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Webp() |
|||
{ |
|||
await using var webpImage = ImageFileHelper.GetWebpTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(webpImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Unsupported); |
|||
|
|||
resizedImage.Result.ShouldBe(webpImage); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public static class ImageFileHelper |
|||
{ |
|||
public static Stream GetJpgTestFileStream() |
|||
{ |
|||
return GetTestFileStream("abp.jpg"); |
|||
} |
|||
|
|||
public static Stream GetPngTestFileStream() |
|||
{ |
|||
return GetTestFileStream("abp.png"); |
|||
} |
|||
|
|||
public static Stream GetWebpTestFileStream() |
|||
{ |
|||
return GetTestFileStream("abp.webp"); |
|||
} |
|||
|
|||
private static Stream GetTestFileStream(string fileName) |
|||
{ |
|||
var assembly = typeof(ImageFileHelper).Assembly; |
|||
var resourceStream = assembly.GetManifestResourceStream("Volo.Abp.Imaging.Files." + fileName); |
|||
if (resourceStream == null) |
|||
{ |
|||
throw new Exception($"File {fileName} does not exists!"); |
|||
} |
|||
|
|||
return resourceStream; |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.test" |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" /> |
|||
<ProjectReference Include="..\Volo.Abp.Imaging.Abstractions.Tests\Volo.Abp.Imaging.Abstractions.Tests.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Imaging.AspNetCore\Volo.Abp.Imaging.AspNetCore.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,11 @@ |
|||
using Volo.Abp.Testing; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public abstract class AbpImagingAspNetCoreTestBase : AbpIntegratedTest<AbpImagingAspNetCoreTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpImagingAspNetCoreModule), |
|||
typeof(AbpTestBaseModule) |
|||
)] |
|||
public class AbpImagingAspNetCoreTestModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Controllers; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class CompressImageAttribute_Tests : AbpImagingAspNetCoreTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task Should_Compressed_IFormFile() |
|||
{ |
|||
var attribute = new CompressImageAttribute(); |
|||
|
|||
var serviceScopeFactory = GetRequiredService<IServiceScopeFactory>(); |
|||
|
|||
await using var stream = ImageFileHelper.GetJpgTestFileStream(); |
|||
|
|||
var actionExecutingContext = new ActionExecutingContext( |
|||
new ActionContext() { |
|||
HttpContext = new DefaultHttpContext() { |
|||
ServiceScopeFactory = serviceScopeFactory |
|||
}, |
|||
RouteData = new Microsoft.AspNetCore.Routing.RouteData(), |
|||
ActionDescriptor = new ControllerActionDescriptor(), |
|||
}, |
|||
new List<IFilterMetadata>(), |
|||
new Dictionary<string, object> |
|||
{ |
|||
{"file", new FormFile(stream, 0, stream.Length, "file", "test.jpg") { |
|||
Headers = new HeaderDictionary(), |
|||
ContentType = "image/jpeg" |
|||
}} |
|||
}, |
|||
new object() |
|||
); |
|||
|
|||
await attribute.OnActionExecutionAsync(actionExecutingContext, async () => await Task.FromResult(new ActionExecutedContext( |
|||
actionExecutingContext, |
|||
new List<IFilterMetadata>(), |
|||
new object() |
|||
))); |
|||
|
|||
actionExecutingContext.ActionArguments["file"].ShouldNotBeNull(); |
|||
actionExecutingContext.ActionArguments["file"].ShouldBeAssignableTo<IFormFile>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Controllers; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ResizeImageAttribute_Tests : AbpImagingAspNetCoreTestBase |
|||
{ |
|||
[Fact] |
|||
public void Should_Init() |
|||
{ |
|||
var attribute = new ResizeImageAttribute(100, 100); |
|||
|
|||
attribute.Width.ShouldBe(100); |
|||
attribute.Height.ShouldBe(100); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resized_IFormFile() |
|||
{ |
|||
var attribute = new ResizeImageAttribute(100, 100); |
|||
|
|||
var serviceScopeFactory = GetRequiredService<IServiceScopeFactory>(); |
|||
await using var stream = ImageFileHelper.GetJpgTestFileStream(); |
|||
|
|||
var actionExecutingContext = new ActionExecutingContext( |
|||
new ActionContext() { |
|||
HttpContext = new DefaultHttpContext() { |
|||
ServiceScopeFactory = serviceScopeFactory |
|||
}, |
|||
RouteData = new Microsoft.AspNetCore.Routing.RouteData(), |
|||
ActionDescriptor = new ControllerActionDescriptor(), |
|||
}, |
|||
new List<IFilterMetadata>(), |
|||
new Dictionary<string, object> |
|||
{ |
|||
{"file", new FormFile(stream, 0, stream.Length, "file", "test.jpg") { |
|||
Headers = new HeaderDictionary(), |
|||
ContentType = "image/jpeg" |
|||
}} |
|||
}, |
|||
new object() |
|||
); |
|||
|
|||
await attribute.OnActionExecutionAsync(actionExecutingContext, async () => await Task.FromResult(new ActionExecutedContext( |
|||
actionExecutingContext, |
|||
new List<IFilterMetadata>(), |
|||
new object() |
|||
))); |
|||
|
|||
actionExecutingContext.ActionArguments["file"].ShouldNotBeNull(); |
|||
actionExecutingContext.ActionArguments["file"].ShouldBeAssignableTo<IFormFile>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.test" |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" /> |
|||
<ProjectReference Include="..\Volo.Abp.Imaging.Abstractions.Tests\Volo.Abp.Imaging.Abstractions.Tests.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Imaging.ImageSharp\Volo.Abp.Imaging.ImageSharp.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,11 @@ |
|||
using Volo.Abp.Testing; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public abstract class AbpImagingImageSharpTestBase : AbpIntegratedTest<AbpImagingImageSharpTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpImagingImageSharpModule), |
|||
typeof(AbpTestBaseModule) |
|||
)] |
|||
|
|||
public class AbpImagingImageSharpTestModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageSharpImageCompressor_Tests : AbpImagingImageSharpTestBase |
|||
{ |
|||
public IImageCompressor ImageCompressor { get; } |
|||
public ImageSharpImageCompressor_Tests() |
|||
{ |
|||
ImageCompressor = GetRequiredService<IImageCompressor>(); |
|||
} |
|||
[Fact] |
|||
public async Task Should_Compress_Jpg() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var compressedImage = await ImageCompressor.CompressAsync(jpegImage); |
|||
|
|||
compressedImage.ShouldNotBeNull(); |
|||
compressedImage.State.ShouldBe(ImageProcessState.Done); |
|||
compressedImage.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
compressedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Png() |
|||
{ |
|||
await using var pngImage = ImageFileHelper.GetPngTestFileStream(); |
|||
var compressedImage = await ImageCompressor.CompressAsync(pngImage); |
|||
|
|||
compressedImage.ShouldNotBeNull(); |
|||
|
|||
if (compressedImage.State == ImageProcessState.Done) |
|||
{ |
|||
compressedImage.Result.Length.ShouldBeLessThan(pngImage.Length); |
|||
}else |
|||
{ |
|||
compressedImage.Result.Length.ShouldBe(pngImage.Length); |
|||
} |
|||
compressedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Webp() |
|||
{ |
|||
await using var webpImage = ImageFileHelper.GetWebpTestFileStream(); |
|||
var compressedImage = await ImageCompressor.CompressAsync(webpImage); |
|||
|
|||
compressedImage.ShouldNotBeNull(); |
|||
compressedImage.State.ShouldBe(ImageProcessState.Done); |
|||
compressedImage.Result.Length.ShouldBeLessThan(webpImage.Length); |
|||
compressedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Stream_And_Byte_Array_The_Same() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var byteArr = await jpegImage.GetAllBytesAsync(); |
|||
|
|||
var compressedImage1 = await ImageCompressor.CompressAsync(jpegImage); |
|||
var compressedImage2 = await ImageCompressor.CompressAsync(byteArr); |
|||
|
|||
compressedImage1.ShouldNotBeNull(); |
|||
compressedImage1.State.ShouldBe(ImageProcessState.Done); |
|||
|
|||
compressedImage2.ShouldNotBeNull(); |
|||
compressedImage2.State.ShouldBe(ImageProcessState.Done); |
|||
|
|||
compressedImage1.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
compressedImage2.Result.LongLength.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
compressedImage1.Result.Length.ShouldBe(compressedImage2.Result.LongLength); |
|||
|
|||
compressedImage1.Result.Dispose(); |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class ImageSharpImageResizer_Tests : AbpImagingImageSharpTestBase |
|||
{ |
|||
public IImageResizer ImageResizer { get; } |
|||
|
|||
public ImageSharpImageResizer_Tests() |
|||
{ |
|||
ImageResizer = GetRequiredService<IImageResizer>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Jpg() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Png() |
|||
{ |
|||
await using var pngImage = ImageFileHelper.GetPngTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(pngImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage.Result.Length.ShouldBeLessThan(pngImage.Length); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Webp() |
|||
{ |
|||
await using var webpImage = ImageFileHelper.GetWebpTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(webpImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage.Result.Length.ShouldBeLessThan(webpImage.Length); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Stream_And_Byte_Array_The_Same() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var byteArr = await jpegImage.GetAllBytesAsync(); |
|||
|
|||
var resizedImage1 = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); |
|||
var resizedImage2 = await ImageResizer.ResizeAsync(byteArr, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage1.ShouldNotBeNull(); |
|||
resizedImage1.State.ShouldBe(ImageProcessState.Done); |
|||
|
|||
resizedImage2.ShouldNotBeNull(); |
|||
resizedImage2.State.ShouldBe(ImageProcessState.Done); |
|||
|
|||
resizedImage1.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
resizedImage2.Result.LongLength.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
resizedImage1.Result.Length.ShouldBe(resizedImage2.Result.LongLength); |
|||
|
|||
resizedImage1.Result.Dispose(); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.test" |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" /> |
|||
<ProjectReference Include="..\Volo.Abp.Imaging.Abstractions.Tests\Volo.Abp.Imaging.Abstractions.Tests.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Imaging.MagickNet\Volo.Abp.Imaging.MagickNet.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Volo\Abp\Imaging\Files\**" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,11 @@ |
|||
using Volo.Abp.Testing; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public abstract class AbpImagingMagickNetTestBase : AbpIntegratedTest<AbpImagingMagickNetTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpImagingMagickNetModule), |
|||
typeof(AbpTestBaseModule) |
|||
)] |
|||
public class AbpImagingMagickNetTestModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class MagickNetImageCompressor_Tests : AbpImagingMagickNetTestBase |
|||
{ |
|||
public IImageCompressor ImageCompressor { get; } |
|||
|
|||
public MagickNetImageCompressor_Tests() |
|||
{ |
|||
ImageCompressor = GetRequiredService<IImageCompressor>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Jpg() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var compressedImage = await ImageCompressor.CompressAsync(jpegImage); |
|||
|
|||
compressedImage.ShouldNotBeNull(); |
|||
compressedImage.State.ShouldBe(ImageProcessState.Done); |
|||
compressedImage.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
compressedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Png() |
|||
{ |
|||
await using var pngImage = ImageFileHelper.GetPngTestFileStream(); |
|||
var compressedImage = await ImageCompressor.CompressAsync(pngImage); |
|||
|
|||
compressedImage.ShouldNotBeNull(); |
|||
compressedImage.State.ShouldBe(ImageProcessState.Done); |
|||
compressedImage.Result.Length.ShouldBeLessThan(pngImage.Length); |
|||
|
|||
compressedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Webp() |
|||
{ |
|||
await using var webpImage = ImageFileHelper.GetWebpTestFileStream(); |
|||
var compressedImage = await ImageCompressor.CompressAsync(webpImage); |
|||
|
|||
compressedImage.ShouldNotBeNull(); |
|||
compressedImage.State.ShouldBe(ImageProcessState.Unsupported); |
|||
compressedImage.Result.ShouldBe(webpImage); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Compress_Stream_And_Byte_Array_The_Same() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var compressedImage1 = await ImageCompressor.CompressAsync(jpegImage); |
|||
var compressedImage2 = await ImageCompressor.CompressAsync(await jpegImage.GetAllBytesAsync()); |
|||
|
|||
compressedImage1.ShouldNotBeNull(); |
|||
compressedImage1.State.ShouldBe(ImageProcessState.Done); |
|||
compressedImage1.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
compressedImage2.ShouldNotBeNull(); |
|||
compressedImage2.State.ShouldBe(ImageProcessState.Done); |
|||
compressedImage2.Result.LongLength.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
compressedImage1.Result.Length.ShouldBe(compressedImage2.Result.LongLength); |
|||
|
|||
compressedImage1.Result.Dispose(); |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class MagickNetImageResizer_Tests : AbpImagingMagickNetTestBase |
|||
{ |
|||
public IImageResizer ImageResizer { get; } |
|||
|
|||
public MagickNetImageResizer_Tests() |
|||
{ |
|||
ImageResizer = GetRequiredService<IImageResizer>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Jpg() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
resizedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Png() |
|||
{ |
|||
await using var pngImage = ImageFileHelper.GetPngTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(pngImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage.Result.Length.ShouldBeLessThan(pngImage.Length); |
|||
|
|||
resizedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Webp() |
|||
{ |
|||
await using var webpImage = ImageFileHelper.GetWebpTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(webpImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage.Result.Length.ShouldBeLessThan(webpImage.Length); |
|||
|
|||
resizedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Stream_And_Byte_Array_The_Same() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var resizedImage1 = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); |
|||
var resizedImage2 = await ImageResizer.ResizeAsync(await jpegImage.GetAllBytesAsync(), new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage1.ShouldNotBeNull(); |
|||
resizedImage1.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage1.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
resizedImage2.ShouldNotBeNull(); |
|||
resizedImage2.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage2.Result.LongLength.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
resizedImage1.Result.Length.ShouldBe(resizedImage2.Result.LongLength); |
|||
|
|||
resizedImage1.Result.Dispose(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue