Merge pull request #17926 from abpframework/AbpImagingSkiaSharpModule

Add `Volo.Abp.Imaging.SkiaSharp` package.
pull/18122/head^2
liangshiwei 2 years ago committed by GitHub
commit 2726d57ea4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -140,6 +140,9 @@
<PackageVersion Include="Shouldly" Version="4.0.3" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.2" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="2.0.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.6" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.6" />
<PackageVersion Include="SkiaSharp.NativeAssets.macOS" Version="2.88.6" />
<PackageVersion Include="Slugify.Core" Version="4.0.1" />
<PackageVersion Include="Spectre.Console" Version="0.47.0" />
<PackageVersion Include="StackExchange.Redis" Version="2.6.122" />

@ -459,6 +459,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Imaging.AspNetCore
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Maui.Client", "src\Volo.Abp.Maui.Client\Volo.Abp.Maui.Client.csproj", "{F19A6E0C-F719-4ED9-A024-14E4B8D40883}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.SkiaSharp", "src\Volo.Abp.Imaging.SkiaSharp\Volo.Abp.Imaging.SkiaSharp.csproj", "{198683D0-7DC6-40F2-B81B-8E446E70A9DE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.SkiaSharp.Tests", "test\Volo.Abp.Imaging.SkiaSharp.Tests\Volo.Abp.Imaging.SkiaSharp.Tests.csproj", "{DFAF8763-D1D6-4EB4-B459-20E31007FE2F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1369,6 +1373,14 @@ Global
{F19A6E0C-F719-4ED9-A024-14E4B8D40883}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F19A6E0C-F719-4ED9-A024-14E4B8D40883}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F19A6E0C-F719-4ED9-A024-14E4B8D40883}.Release|Any CPU.Build.0 = Release|Any CPU
{198683D0-7DC6-40F2-B81B-8E446E70A9DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{198683D0-7DC6-40F2-B81B-8E446E70A9DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{198683D0-7DC6-40F2-B81B-8E446E70A9DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{198683D0-7DC6-40F2-B81B-8E446E70A9DE}.Release|Any CPU.Build.0 = Release|Any CPU
{DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1600,6 +1612,8 @@ Global
{62B2B8C9-8F24-4D31-894F-C1F0728D32AB} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{983B0136-384B-4439-B374-31111FFAA286} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{F19A6E0C-F719-4ED9-A024-14E4B8D40883} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{198683D0-7DC6-40F2-B81B-8E446E70A9DE} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{DFAF8763-D1D6-4EB4-B459-20E31007FE2F} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

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

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<PackageId>Volo.Abp.Imaging.SkiaSharp</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="SkiaSharp" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
</ItemGroup>
</Project>

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

@ -0,0 +1,98 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using SkiaSharp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
namespace Volo.Abp.Imaging;
public class SkiaSharpImageResizerContributor : IImageResizerContributor, ITransientDependency
{
protected SkiaSharpResizerOptions Options { get; }
public SkiaSharpImageResizerContributor(IOptions<SkiaSharpResizerOptions> options)
{
Options = options.Value;
}
public virtual async Task<ImageResizeResult<byte[]>> TryResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, string? mimeType = null, CancellationToken cancellationToken = default)
{
if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType))
{
return new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported);
}
using (var memoryStream = new MemoryStream(bytes))
{
var result = await TryResizeAsync(memoryStream, 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);
}
}
public virtual async Task<ImageResizeResult<Stream>> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, string? mimeType = null, CancellationToken cancellationToken = default)
{
if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType))
{
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported);
}
var (memoryBitmapStream, memorySkCodecStream) = await CreateMemoryStream(stream);
using (var original = SKBitmap.Decode(memoryBitmapStream))
{
using (var resized = original.Resize(new SKImageInfo(resizeArgs.Width, resizeArgs.Height), Options.SKFilterQuality))
{
using (var image = SKImage.FromBitmap(resized))
{
using (var codec = SKCodec.Create(memorySkCodecStream))
{
var memoryStream = new MemoryStream();
image.Encode(codec.EncodedFormat, Options.Quality).SaveTo(memoryStream);
return new ImageResizeResult<Stream>(memoryStream, ImageProcessState.Done);
}
}
}
}
}
protected virtual async Task<(MemoryStream, MemoryStream)> CreateMemoryStream(Stream stream)
{
var streamPosition = stream.Position;
var memoryBitmapStream = new MemoryStream();
var memorySkCodecStream = new MemoryStream();
await stream.CopyToAsync(memoryBitmapStream);
stream.Position = streamPosition;
await stream.CopyToAsync(memorySkCodecStream);
stream.Position = streamPosition;
memoryBitmapStream.Position = 0;
memorySkCodecStream.Position = 0;
return (memoryBitmapStream, memorySkCodecStream);
}
protected virtual bool CanResize(string? mimeType)
{
return mimeType switch {
MimeTypes.Image.Jpeg => true,
MimeTypes.Image.Png => true,
MimeTypes.Image.Webp => true,
_ => false
};
}
}

@ -0,0 +1,16 @@
using SkiaSharp;
namespace Volo.Abp.Imaging;
public class SkiaSharpResizerOptions
{
public SKFilterQuality SKFilterQuality { get; set; }
public int Quality { get; set; }
public SkiaSharpResizerOptions()
{
SKFilterQuality = SKFilterQuality.None;
Quality = 75;
}
}

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<ProjectReference Include="..\Volo.Abp.Imaging.Abstractions.Tests\Volo.Abp.Imaging.Abstractions.Tests.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Imaging.SkiaSharp\Volo.Abp.Imaging.SkiaSharp.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Abp\Imaging\Files\**" />
</ItemGroup>
</Project>

@ -0,0 +1,14 @@
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
namespace Volo.Abp.Imaging;
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpImagingSkiaSharpModule),
typeof(AbpTestBaseModule)
)]
public class AbpImagingSkiaSharpTestModule : AbpModule
{
}

@ -0,0 +1,11 @@
using Volo.Abp.Testing;
namespace Volo.Abp.Imaging;
public abstract class AbpImagingSkiaSharpTestBase : AbpIntegratedTest<AbpImagingSkiaSharpTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}

@ -0,0 +1,75 @@
using System.IO;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.Imaging;
public class SkiaSharpImageResizerTests : AbpImagingSkiaSharpTestBase
{
public IImageResizer ImageResizer { get; }
public SkiaSharpImageResizerTests()
{
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();
}
}

@ -204,6 +204,7 @@ $projects = (
"framework/src/Volo.Abp.Imaging.AspNetCore",
"framework/src/Volo.Abp.Imaging.ImageSharp",
"framework/src/Volo.Abp.Imaging.MagickNet",
"framework/src/Volo.Abp.Imaging.SkiaSharp",
"framework/src/Volo.Abp.Json",
"framework/src/Volo.Abp.Json.Abstractions",
"framework/src/Volo.Abp.Json.Newtonsoft",

Loading…
Cancel
Save