Browse Source

Merge pull request #16586 from abpframework/issue-16206

Add Volo.Abp.Image package
pull/16699/head
Halil İbrahim Kalkan 3 years ago
committed by GitHub
parent
commit
9a430f2c3e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 307
      docs/en/Image-Manipulation.md
  2. 4
      docs/en/docs-nav.json
  3. 56
      framework/Volo.Abp.sln
  4. 63
      framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs
  5. 0
      framework/src/Volo.Abp.Core/Volo/Abp/Http/MimeTypes.cs
  6. 3
      framework/src/Volo.Abp.Imaging.Abstractions/FodyWeavers.xml
  7. 30
      framework/src/Volo.Abp.Imaging.Abstractions/FodyWeavers.xsd
  8. 3
      framework/src/Volo.Abp.Imaging.Abstractions/Volo.Abp.Imaging.Abstractions.abppkg.json
  9. 20
      framework/src/Volo.Abp.Imaging.Abstractions/Volo.Abp.Imaging.Abstractions.csproj
  10. 9
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/AbpImagingAbstractionsModule.cs
  11. 21
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageCompressor.cs
  12. 18
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageCompressorContributor.cs
  13. 23
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageResizer.cs
  14. 21
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageResizerContributor.cs
  15. 8
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressResult.cs
  16. 66
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs
  17. 13
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageProcessResult.cs
  18. 8
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageProcessState.cs
  19. 49
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeArgs.cs
  20. 13
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeMode.cs
  21. 6
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeOptions.cs
  22. 8
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeResult.cs
  23. 87
      framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs
  24. 3
      framework/src/Volo.Abp.Imaging.AspNetCore/FodyWeavers.xml
  25. 30
      framework/src/Volo.Abp.Imaging.AspNetCore/FodyWeavers.xsd
  26. 3
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo.Abp.Imaging.AspNetCore.abppkg.json
  27. 23
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo.Abp.Imaging.AspNetCore.csproj
  28. 9
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/AbpImagingAspNetCoreModule.cs
  29. 104
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/CompressImageAttribute.cs
  30. 117
      framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/ResizeImageAttribute.cs
  31. 3
      framework/src/Volo.Abp.Imaging.ImageSharp/FodyWeavers.xml
  32. 30
      framework/src/Volo.Abp.Imaging.ImageSharp/FodyWeavers.xsd
  33. 24
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo.Abp.Imaging.ImageSharp.csproj
  34. 3
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo.Abp.Imaging.ImageSharp.json
  35. 8
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/AbpImagingImageSharpModule.cs
  36. 32
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpCompressOptions.cs
  37. 124
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageCompressorContributor.cs
  38. 124
      framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageResizerContributor.cs
  39. 3
      framework/src/Volo.Abp.Imaging.MagickNet/FodyWeavers.xml
  40. 30
      framework/src/Volo.Abp.Imaging.MagickNet/FodyWeavers.xsd
  41. 3
      framework/src/Volo.Abp.Imaging.MagickNet/Volo.Abp.Imaging.MagickNet.abppkg.json
  42. 24
      framework/src/Volo.Abp.Imaging.MagickNet/Volo.Abp.Imaging.MagickNet.csproj
  43. 8
      framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/AbpImagingMagickNetModule.cs
  44. 103
      framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickImageCompressorContributor.cs
  45. 325
      framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickImageResizerContributor.cs
  46. 8
      framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickNetCompressOptions.cs
  47. 3
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo.Abp.Imaging.Abstractions.Tests.abppkg.json
  48. 20
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo.Abp.Imaging.Abstractions.Tests.csproj
  49. 11
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/AbpImagingAbstractionsTestBase.cs
  50. 14
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/AbpImagingAbstractionsTestModule.cs
  51. BIN
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.jpg
  52. BIN
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.png
  53. BIN
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.webp
  54. 51
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/IImageCompressor_Tests.cs
  55. 50
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/IImageResizer_Tests.cs
  56. 34
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/ImageFileHelper.cs
  57. 3
      framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo.Abp.Imaging.AspNetCore.Tests.abppkg.json
  58. 15
      framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo.Abp.Imaging.AspNetCore.Tests.csproj
  59. 11
      framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo/Abp/Imaging/AbpImagingAspNetCoreTestBase.cs
  60. 14
      framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo/Abp/Imaging/AbpImagingAspNetCoreTestModule.cs
  61. 52
      framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo/Abp/Imaging/CompressImageAttribute_Tests.cs
  62. 60
      framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo/Abp/Imaging/ResizeImageAttribute_Tests.cs
  63. 3
      framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo.Abp.Imaging.ImageSharp.Tests.abppkg.json
  64. 16
      framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo.Abp.Imaging.ImageSharp.Tests.csproj
  65. 11
      framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo/Abp/Imaging/AbpImagingImageSharpTestBase.cs
  66. 15
      framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo/Abp/Imaging/AbpImagingImageSharpTestModule.cs
  67. 79
      framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo/Abp/Imaging/ImageSharpImageCompressor_Tests.cs
  68. 72
      framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo/Abp/Imaging/ImageSharpImageResizer_Tests.cs
  69. 3
      framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo.Abp.Imaging.MagickNet.Tests.abppkg.json
  70. 19
      framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo.Abp.Imaging.MagickNet.Tests.csproj
  71. 11
      framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo/Abp/Imaging/AbpImagingMagickNetTestBase.cs
  72. 14
      framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo/Abp/Imaging/AbpImagingMagickNetTestModule.cs
  73. 73
      framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo/Abp/Imaging/MagickNetImageCompressor_Tests.cs
  74. 75
      framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo/Abp/Imaging/MagickNetImageResizer_Tests.cs
  75. 4
      nupkg/common.ps1

307
docs/en/Image-Manipulation.md

@ -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)
{
//...
}
```

4
docs/en/docs-nav.json

@ -417,6 +417,10 @@
"text": "GUID Generation",
"path": "Guid-Generation.md"
},
{
"text": "Image Manipulation",
"path": "Image-Manipulation.md"
},
{
"text": "JSON",
"path": "JSON.md"

56
framework/Volo.Abp.sln

@ -439,6 +439,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Ldap.Abstractions"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Ddd.Domain.Shared", "src\Volo.Abp.Ddd.Domain.Shared\Volo.Abp.Ddd.Domain.Shared.csproj", "{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.Abstractions", "src\Volo.Abp.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj", "{32F3E84B-D02E-42BD-BC5C-0D211564EF30}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.AspNetCore", "src\Volo.Abp.Imaging.AspNetCore\Volo.Abp.Imaging.AspNetCore.csproj", "{78340A37-219E-4F2D-9AC6-40A7B467EEEC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.ImageSharp", "src\Volo.Abp.Imaging.ImageSharp\Volo.Abp.Imaging.ImageSharp.csproj", "{44467427-E0BE-492C-B9B4-82B362C183C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.MagickNet", "src\Volo.Abp.Imaging.MagickNet\Volo.Abp.Imaging.MagickNet.csproj", "{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.Abstractions.Tests", "test\Volo.Abp.Imaging.Abstractions.Tests\Volo.Abp.Imaging.Abstractions.Tests.csproj", "{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.ImageSharp.Tests", "test\Volo.Abp.Imaging.ImageSharp.Tests\Volo.Abp.Imaging.ImageSharp.Tests.csproj", "{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.MagickNet.Tests", "test\Volo.Abp.Imaging.MagickNet.Tests\Volo.Abp.Imaging.MagickNet.Tests.csproj", "{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.AspNetCore.Tests", "test\Volo.Abp.Imaging.AspNetCore.Tests\Volo.Abp.Imaging.AspNetCore.Tests.csproj", "{983B0136-384B-4439-B374-31111FFAA286}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1309,6 +1325,38 @@ Global
{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Release|Any CPU.Build.0 = Release|Any CPU
{32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Release|Any CPU.Build.0 = Release|Any CPU
{78340A37-219E-4F2D-9AC6-40A7B467EEEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78340A37-219E-4F2D-9AC6-40A7B467EEEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78340A37-219E-4F2D-9AC6-40A7B467EEEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78340A37-219E-4F2D-9AC6-40A7B467EEEC}.Release|Any CPU.Build.0 = Release|Any CPU
{44467427-E0BE-492C-B9B4-82B362C183C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44467427-E0BE-492C-B9B4-82B362C183C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44467427-E0BE-492C-B9B4-82B362C183C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44467427-E0BE-492C-B9B4-82B362C183C3}.Release|Any CPU.Build.0 = Release|Any CPU
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC}.Release|Any CPU.Build.0 = Release|Any CPU
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF}.Release|Any CPU.Build.0 = Release|Any CPU
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5}.Release|Any CPU.Build.0 = Release|Any CPU
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB}.Release|Any CPU.Build.0 = Release|Any CPU
{983B0136-384B-4439-B374-31111FFAA286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{983B0136-384B-4439-B374-31111FFAA286}.Debug|Any CPU.Build.0 = Debug|Any CPU
{983B0136-384B-4439-B374-31111FFAA286}.Release|Any CPU.ActiveCfg = Release|Any CPU
{983B0136-384B-4439-B374-31111FFAA286}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1530,6 +1578,14 @@ Global
{8764DFAF-D13D-449A-9A5E-5D7F0B2D7FEF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{0F80E95C-41E6-4F23-94FF-FC9D0B8D5D71} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{0858571B-CE73-4AD6-BD06-EC9F0714D8E9} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{32F3E84B-D02E-42BD-BC5C-0D211564EF30} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{44467427-E0BE-492C-B9B4-82B362C183C3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{78340A37-219E-4F2D-9AC6-40A7B467EEEC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{2BE6BDC7-A9A3-4E30-9099-A9EF4813F6FF} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{1E161A34-10C1-46FA-9EFD-10DD0858A8F5} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{983B0136-384B-4439-B374-31111FFAA286} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

63
framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs

@ -7,28 +7,28 @@ public static class AbpStreamExtensions
{
public static byte[] GetAllBytes(this Stream stream)
{
using (var memoryStream = new MemoryStream())
if (stream is MemoryStream memoryStream)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
using (var ms = stream.CreateMemoryStream())
{
return ms.ToArray();
}
}
public static async Task<byte[]> GetAllBytesAsync(this Stream stream, CancellationToken cancellationToken = default)
{
using (var memoryStream = new MemoryStream())
if (stream is MemoryStream memoryStream)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
await stream.CopyToAsync(memoryStream, cancellationToken);
return memoryStream.ToArray();
}
using (var ms = await stream.CreateMemoryStreamAsync(cancellationToken))
{
return ms.ToArray();
}
}
public static Task CopyToAsync(this Stream stream, Stream destination, CancellationToken cancellationToken)
@ -37,10 +37,49 @@ public static class AbpStreamExtensions
{
stream.Position = 0;
}
return stream.CopyToAsync(
destination,
81920, //this is already the default value, but needed to set to be able to pass the cancellationToken
cancellationToken
);
}
public async static Task<MemoryStream> CreateMemoryStreamAsync(this Stream stream, CancellationToken cancellationToken = default)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, cancellationToken);
if (stream.CanSeek)
{
stream.Position = 0;
}
memoryStream.Position = 0;
return memoryStream;
}
public static MemoryStream CreateMemoryStream(this Stream stream)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
if (stream.CanSeek)
{
stream.Position = 0;
}
memoryStream.Position = 0;
return memoryStream;
}
}

0
framework/src/Volo.Abp.Http/Volo/Abp/Http/MimeTypes.cs → framework/src/Volo.Abp.Core/Volo/Abp/Http/MimeTypes.cs

3
framework/src/Volo.Abp.Imaging.Abstractions/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.Imaging.Abstractions/FodyWeavers.xsd

@ -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>

3
framework/src/Volo.Abp.Imaging.Abstractions/Volo.Abp.Imaging.Abstractions.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

20
framework/src/Volo.Abp.Imaging.Abstractions/Volo.Abp.Imaging.Abstractions.csproj

@ -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>

9
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/AbpImagingAbstractionsModule.cs

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
namespace Volo.Abp.Imaging;
[DependsOn(typeof(AbpThreadingModule))]
public class AbpImagingAbstractionsModule : AbpModule
{
}

21
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageCompressor.cs

@ -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
);
}

18
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageCompressorContributor.cs

@ -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);
}

23
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageResizer.cs

@ -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
);
}

21
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/IImageResizerContributor.cs

@ -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);
}

8
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressResult.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.Imaging;
public class ImageCompressResult<T> : ImageProcessResult<T>
{
public ImageCompressResult(T result, ImageProcessState state) : base(result, state)
{
}
}

66
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs

@ -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);
}
}

13
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageProcessResult.cs

@ -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;
}
}

8
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageProcessState.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.Imaging;
public enum ImageProcessState : byte
{
Done = 1,
Canceled = 2,
Unsupported = 3,
}

49
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeArgs.cs

@ -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;
}
}

13
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeMode.cs

@ -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
}

6
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeOptions.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.Imaging;
public class ImageResizeOptions
{
public ImageResizeMode DefaultResizeMode { get; set; } = ImageResizeMode.None;
}

8
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizeResult.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.Imaging;
public class ImageResizeResult<T> : ImageProcessResult<T>
{
public ImageResizeResult(T result, ImageProcessState state) : base(result, state)
{
}
}

87
framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs

@ -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;
}
}
}

3
framework/src/Volo.Abp.Imaging.AspNetCore/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.Imaging.AspNetCore/FodyWeavers.xsd

@ -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>

3
framework/src/Volo.Abp.Imaging.AspNetCore/Volo.Abp.Imaging.AspNetCore.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

23
framework/src/Volo.Abp.Imaging.AspNetCore/Volo.Abp.Imaging.AspNetCore.csproj

@ -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>

9
framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/AbpImagingAspNetCoreModule.cs

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.Imaging;
[DependsOn(typeof(AbpImagingAbstractionsModule))]
public class AbpImagingAspNetCoreModule : AbpModule
{
}

104
framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/CompressImageAttribute.cs

@ -0,0 +1,104 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Content;
namespace Volo.Abp.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;
}
}

117
framework/src/Volo.Abp.Imaging.AspNetCore/Volo/Abp/Imaging/ResizeImageAttribute.cs

@ -0,0 +1,117 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Content;
namespace Volo.Abp.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;
}
}

3
framework/src/Volo.Abp.Imaging.ImageSharp/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.Imaging.ImageSharp/FodyWeavers.xsd

@ -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>

24
framework/src/Volo.Abp.Imaging.ImageSharp/Volo.Abp.Imaging.ImageSharp.csproj

@ -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>

3
framework/src/Volo.Abp.Imaging.ImageSharp/Volo.Abp.Imaging.ImageSharp.json

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

8
framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/AbpImagingImageSharpModule.cs

@ -0,0 +1,8 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.Imaging;
[DependsOn(typeof(AbpImagingAbstractionsModule))]
public class AbpImagingImageSharpModule : AbpModule
{
}

32
framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpCompressOptions.cs

@ -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
};
}
}

124
framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageCompressorContributor.cs

@ -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}");
}
}
}

124
framework/src/Volo.Abp.Imaging.ImageSharp/Volo/Abp/Imaging/ImageSharpImageResizerContributor.cs

@ -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;
}
}

3
framework/src/Volo.Abp.Imaging.MagickNet/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.Imaging.MagickNet/FodyWeavers.xsd

@ -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>

3
framework/src/Volo.Abp.Imaging.MagickNet/Volo.Abp.Imaging.MagickNet.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

24
framework/src/Volo.Abp.Imaging.MagickNet/Volo.Abp.Imaging.MagickNet.csproj

@ -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>

8
framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/AbpImagingMagickNetModule.cs

@ -0,0 +1,8 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.Imaging;
[DependsOn(typeof(AbpImagingAbstractionsModule))]
public class AbpImagingMagickNetModule : AbpModule
{
}

103
framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickImageCompressorContributor.cs

@ -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);
}
}

325
framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickImageResizerContributor.cs

@ -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;
}
}

8
framework/src/Volo.Abp.Imaging.MagickNet/Volo/Abp/Imaging/MagickNetCompressOptions.cs

@ -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; }
}

3
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo.Abp.Imaging.Abstractions.Tests.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.test"
}

20
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo.Abp.Imaging.Abstractions.Tests.csproj

@ -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>

11
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/AbpImagingAbstractionsTestBase.cs

@ -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();
}
}

14
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/AbpImagingAbstractionsTestModule.cs

@ -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
{
}

BIN
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

BIN
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

51
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/IImageCompressor_Tests.cs

@ -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);
}
}

50
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/IImageResizer_Tests.cs

@ -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);
}
}

34
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/ImageFileHelper.cs

@ -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;
}
}

3
framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo.Abp.Imaging.AspNetCore.Tests.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.test"
}

15
framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo.Abp.Imaging.AspNetCore.Tests.csproj

@ -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>

11
framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo/Abp/Imaging/AbpImagingAspNetCoreTestBase.cs

@ -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();
}
}

14
framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo/Abp/Imaging/AbpImagingAspNetCoreTestModule.cs

@ -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
{
}

52
framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo/Abp/Imaging/CompressImageAttribute_Tests.cs

@ -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>();
}
}

60
framework/test/Volo.Abp.Imaging.AspNetCore.Tests/Volo/Abp/Imaging/ResizeImageAttribute_Tests.cs

@ -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>();
}
}

3
framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo.Abp.Imaging.ImageSharp.Tests.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.test"
}

16
framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo.Abp.Imaging.ImageSharp.Tests.csproj

@ -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>

11
framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo/Abp/Imaging/AbpImagingImageSharpTestBase.cs

@ -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();
}
}

15
framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo/Abp/Imaging/AbpImagingImageSharpTestModule.cs

@ -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
{
}

79
framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo/Abp/Imaging/ImageSharpImageCompressor_Tests.cs

@ -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();
}
}

72
framework/test/Volo.Abp.Imaging.ImageSharp.Tests/Volo/Abp/Imaging/ImageSharpImageResizer_Tests.cs

@ -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();
}
}

3
framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo.Abp.Imaging.MagickNet.Tests.abppkg.json

@ -0,0 +1,3 @@
{
"role": "lib.test"
}

19
framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo.Abp.Imaging.MagickNet.Tests.csproj

@ -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>

11
framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo/Abp/Imaging/AbpImagingMagickNetTestBase.cs

@ -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();
}
}

14
framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo/Abp/Imaging/AbpImagingMagickNetTestModule.cs

@ -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
{
}

73
framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo/Abp/Imaging/MagickNetImageCompressor_Tests.cs

@ -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();
}
}

75
framework/test/Volo.Abp.Imaging.MagickNet.Tests/Volo/Abp/Imaging/MagickNetImageResizer_Tests.cs

@ -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();
}
}

4
nupkg/common.ps1

@ -200,6 +200,10 @@ $projects = (
"framework/src/Volo.Abp.Http.Client.IdentityModel.MauiBlazor",
"framework/src/Volo.Abp.Http",
"framework/src/Volo.Abp.IdentityModel",
"framework/src/Volo.Abp.Imaging.Abstractions",
"framework/src/Volo.Abp.Imaging.AspNetCore",
"framework/src/Volo.Abp.Imaging.ImageSharp",
"framework/src/Volo.Abp.Imaging.MagickNet",
"framework/src/Volo.Abp.Json",
"framework/src/Volo.Abp.Json.Abstractions",
"framework/src/Volo.Abp.Json.Newtonsoft",

Loading…
Cancel
Save