feat:完善模块化+缩略图模块
@@ -83,7 +83,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.RBAC.Application", "src\
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.RBAC.Web", "src\project\rbac\Yi.RBAC.Web\Yi.RBAC.Web.csproj", "{0C031C7D-6F80-4559-977C-AC001036EC44}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.RBAC.Web", "src\project\rbac\Yi.RBAC.Web\Yi.RBAC.Web.csproj", "{0C031C7D-6F80-4559-977C-AC001036EC44}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ThumbnailSharp", "src\module\Yi.Framework.ThumbnailSharp\Yi.Framework.ThumbnailSharp.csproj", "{60E54034-792C-4A90-BCDF-4D5FFB45089E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ImageSharp", "src\module\Yi.Framework.ImageSharp\Yi.Framework.ImageSharp.csproj", "{60E54034-792C-4A90-BCDF-4D5FFB45089E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.EventBus", "src\module\Yi.Framework.EventBus\Yi.Framework.EventBus.csproj", "{FC559052-36EC-4379-B447-298FAD6045F4}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.EventBus", "src\module\Yi.Framework.EventBus\Yi.Framework.EventBus.csproj", "{FC559052-36EC-4379-B447-298FAD6045F4}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -103,7 +103,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.MultiTenancy",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.DictionaryManager", "src\module\Yi.Framework.DictionaryManager\Yi.Framework.DictionaryManager.csproj", "{8941B30D-698B-477A-8737-43E7B4A8695A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.DictionaryManager", "src\module\Yi.Framework.DictionaryManager\Yi.Framework.DictionaryManager.csproj", "{8941B30D-698B-477A-8737-43E7B4A8695A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Sms.Aliyun", "src\module\Yi.Framework.Sms.Aliyun\Yi.Framework.Sms.Aliyun.csproj", "{063178CF-C5B9-463C-A8A4-F32B743818E2}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Sms.Aliyun", "src\module\Yi.Framework.Sms.Aliyun\Yi.Framework.Sms.Aliyun.csproj", "{063178CF-C5B9-463C-A8A4-F32B743818E2}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ namespace Yi.Framework.Core.Extensions
|
|||||||
public static WebApplicationBuilder UseYiModules(this WebApplicationBuilder builder, Type startType)
|
public static WebApplicationBuilder UseYiModules(this WebApplicationBuilder builder, Type startType)
|
||||||
{
|
{
|
||||||
var moduleManager = new ModuleManager(startType);
|
var moduleManager = new ModuleManager(startType);
|
||||||
moduleManager.Invoker();
|
|
||||||
|
|
||||||
Assembly[] assemblies2 = moduleManager.ToAssemblyArray();
|
|
||||||
|
Assembly[] assemblies2 = moduleManager.ToAssemblyArray(moduleManager.Invoker());
|
||||||
return builder.UseStartupModules(delegate (StartupModulesOptions options)
|
return builder.UseStartupModules(delegate (StartupModulesOptions options)
|
||||||
{
|
{
|
||||||
options.DiscoverStartupModules(assemblies2);
|
options.DiscoverStartupModules(assemblies2);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Yi.Framework.Core.Helper
|
|||||||
|
|
||||||
public static List<string> ImageType { get; set; } = new List<string>
|
public static List<string> ImageType { get; set; } = new List<string>
|
||||||
{
|
{
|
||||||
".jpg",".png",".jpge",".gif"
|
".jpg",".png",".jpge"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static Hashtable _mimeMappingTable;
|
private static Hashtable _mimeMappingTable;
|
||||||
|
|||||||
@@ -12,17 +12,30 @@ namespace Yi.Framework.Core.Module
|
|||||||
|
|
||||||
internal class ModuleManager
|
internal class ModuleManager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 全部程序集
|
||||||
|
/// </summary>
|
||||||
private List<Type> ResultType = new List<Type>();
|
private List<Type> ResultType = new List<Type>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始程序集
|
||||||
|
/// </summary>
|
||||||
private Type StartType;
|
private Type StartType;
|
||||||
public ModuleManager(Type startType)
|
public ModuleManager(Type startType)
|
||||||
{
|
{
|
||||||
StartType = startType;
|
StartType = startType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
public List<Type> Invoker()
|
public List<Type> Invoker()
|
||||||
{
|
{
|
||||||
StartBFSNodes(StartType);
|
StartBFSNodes(StartType);
|
||||||
var result= RemoveDuplicate(ResultType);
|
ResultType= ResultType.Distinct().ToList();
|
||||||
|
var result = StartTopologicalSortNodes().Reverse().ToList();
|
||||||
|
|
||||||
Logger? _logger = LogManager.Setup().LoadConfigurationFromAssemblyResource(typeof(ModuleManager).Assembly).GetCurrentClassLogger();
|
Logger? _logger = LogManager.Setup().LoadConfigurationFromAssemblyResource(typeof(ModuleManager).Assembly).GetCurrentClassLogger();
|
||||||
foreach (var r in result)
|
foreach (var r in result)
|
||||||
{
|
{
|
||||||
@@ -33,17 +46,37 @@ namespace Yi.Framework.Core.Module
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Type[]? GetDependsOnType(Type type)
|
|
||||||
{
|
|
||||||
var dependsOnbuild = type.GetCustomAttributes(typeof(DependsOnAttribute), false).FirstOrDefault() as DependsOnAttribute;
|
|
||||||
if (dependsOnbuild is null)
|
|
||||||
{
|
|
||||||
return new Type[0];
|
|
||||||
}
|
|
||||||
return dependsOnbuild.GetDependedTypes();
|
|
||||||
|
|
||||||
|
private Type[] StartTopologicalSortNodes()
|
||||||
|
{
|
||||||
|
List<TopologicalSortNode<Type>> topologicalSortNodes = new List<TopologicalSortNode<Type>>();
|
||||||
|
|
||||||
|
|
||||||
|
//添加注册到节点
|
||||||
|
foreach (var res in ResultType)
|
||||||
|
{
|
||||||
|
var typeNode = new TopologicalSortNode<Type>(res);
|
||||||
|
topologicalSortNodes.Add(typeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<Type, TopologicalSortNode<Type>> nodeDic = topologicalSortNodes.ToDictionary(x => x.Data);
|
||||||
|
|
||||||
|
|
||||||
|
//各个节点互相添加依赖
|
||||||
|
foreach (var node in topologicalSortNodes)
|
||||||
|
{
|
||||||
|
GetDependsOnType(node.Data)?.ToList().ForEach(x => node.AddDependent(nodeDic[x]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return TopologicalSortNode<Type>.TopologicalSort(topologicalSortNodes).Select(x => x.Data).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BFS获取全部程序集
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node"></param>
|
||||||
private void StartBFSNodes(Type node)
|
private void StartBFSNodes(Type node)
|
||||||
{
|
{
|
||||||
ResultType.Add(node);
|
ResultType.Add(node);
|
||||||
@@ -57,29 +90,30 @@ namespace Yi.Framework.Core.Module
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Type> RemoveDuplicate(List<Type> array)
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取模块 需依赖的模块
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private Type[]? GetDependsOnType(Type type)
|
||||||
{
|
{
|
||||||
HashSet<Type> s = new HashSet<Type>();
|
var dependsOnbuild = type.GetCustomAttributes(typeof(DependsOnAttribute), false).FirstOrDefault() as DependsOnAttribute;
|
||||||
List<Type> list = new List<Type>();
|
if (dependsOnbuild is null)
|
||||||
for (int i = array.Count - 1; i >= 0; i--)
|
|
||||||
{
|
{
|
||||||
if (!s.Contains(array[i]))
|
return new Type[0];
|
||||||
{
|
|
||||||
s.Add(array[i]);
|
|
||||||
list.Add(array[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ResultType = list;
|
return dependsOnbuild.GetDependedTypes();
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<Assembly> ToAssemblyList()
|
public List<Assembly> ToAssemblyList()
|
||||||
{
|
{
|
||||||
return ResultType.Select(a => a.Assembly).ToList();
|
return ResultType.Select(a => a.Assembly).ToList();
|
||||||
}
|
}
|
||||||
public Assembly[] ToAssemblyArray()
|
public Assembly[] ToAssemblyArray(List<Type> types)
|
||||||
{
|
{
|
||||||
return ResultType.Select(a => a.Assembly).ToArray();
|
return types.Select(a => a.Assembly).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Yi.Framework.Core.Module
|
||||||
|
{
|
||||||
|
public class TopologicalSortNode<T>
|
||||||
|
{
|
||||||
|
public T Data { get; private set; }
|
||||||
|
public List<TopologicalSortNode<T>> Dependents { get; private set; }
|
||||||
|
public int IncomingEdges { get; private set; }
|
||||||
|
public TopologicalSortNode(T data)
|
||||||
|
{
|
||||||
|
Data = data;
|
||||||
|
Dependents = new List<TopologicalSortNode<T>>();
|
||||||
|
IncomingEdges = 0;
|
||||||
|
}
|
||||||
|
public void AddDependent(TopologicalSortNode<T> dependent)
|
||||||
|
{
|
||||||
|
Dependents.Add(dependent);
|
||||||
|
dependent.IncomingEdges++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<TopologicalSortNode<T>> TopologicalSort(List<TopologicalSortNode<T>> graph)
|
||||||
|
{
|
||||||
|
List<TopologicalSortNode<T>> result = new List<TopologicalSortNode<T>>();
|
||||||
|
Queue<TopologicalSortNode<T>> queue = new Queue<TopologicalSortNode<T>>();
|
||||||
|
// 将所有入度为 0 的节点加入队列
|
||||||
|
foreach (TopologicalSortNode<T> node in graph)
|
||||||
|
{
|
||||||
|
if (node.IncomingEdges == 0)
|
||||||
|
{
|
||||||
|
queue.Enqueue(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 依次将入度为 0 的节点出队,并将它的依赖节点的入度减 1
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
TopologicalSortNode<T> node = queue.Dequeue();
|
||||||
|
result.Add(node);
|
||||||
|
foreach (TopologicalSortNode<T> dependent in node.Dependents)
|
||||||
|
{
|
||||||
|
dependent.IncomingEdges--;
|
||||||
|
if (dependent.IncomingEdges == 0)
|
||||||
|
{
|
||||||
|
queue.Enqueue(dependent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果存在入度不为 0 的节点,则说明图中存在环
|
||||||
|
foreach (TopologicalSortNode<T> node in graph)
|
||||||
|
{
|
||||||
|
if (node.IncomingEdges != 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("模块之间存在互相依赖!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ using Yi.Framework.Core.Helper;
|
|||||||
using Yi.Framework.Ddd.Repositories;
|
using Yi.Framework.Ddd.Repositories;
|
||||||
using Yi.Framework.Ddd.Services;
|
using Yi.Framework.Ddd.Services;
|
||||||
using Yi.Framework.Ddd.Services.Abstract;
|
using Yi.Framework.Ddd.Services.Abstract;
|
||||||
using Yi.Framework.ThumbnailSharp;
|
using Yi.Framework.ImageSharp;
|
||||||
|
|
||||||
namespace Yi.Framework.FileManager
|
namespace Yi.Framework.FileManager
|
||||||
{
|
{
|
||||||
@@ -27,13 +27,13 @@ namespace Yi.Framework.FileManager
|
|||||||
public class FileService : ApplicationService, IFileService, IAutoApiService
|
public class FileService : ApplicationService, IFileService, IAutoApiService
|
||||||
{
|
{
|
||||||
private readonly IRepository<FileEntity> _repository;
|
private readonly IRepository<FileEntity> _repository;
|
||||||
private readonly ThumbnailSharpManager _thumbnailSharpManager;
|
private readonly ImageSharpManager _imageSharpManager;
|
||||||
private readonly HttpContext _httpContext;
|
private readonly HttpContext _httpContext;
|
||||||
public FileService(IRepository<FileEntity> repository, ThumbnailSharpManager thumbnailSharpManager, IHttpContextAccessor httpContextAccessor
|
public FileService(IRepository<FileEntity> repository, ImageSharpManager imageSharpManager, IHttpContextAccessor httpContextAccessor
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_thumbnailSharpManager = thumbnailSharpManager;
|
_imageSharpManager = imageSharpManager;
|
||||||
if (httpContextAccessor.HttpContext is null)
|
if (httpContextAccessor.HttpContext is null)
|
||||||
{
|
{
|
||||||
throw new ApplicationException("HttpContext为空");
|
throw new ApplicationException("HttpContext为空");
|
||||||
@@ -125,21 +125,16 @@ namespace Yi.Framework.FileManager
|
|||||||
{
|
{
|
||||||
Directory.CreateDirectory(thumbnailPath);
|
Directory.CreateDirectory(thumbnailPath);
|
||||||
}
|
}
|
||||||
//保存至缩略图路径
|
string thumbnailFilePath = Path.Combine(thumbnailPath, filename);
|
||||||
byte[] result = null!;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = _thumbnailSharpManager.CreateThumbnailBytes(thumbnailSize: 300, imageStream: stream, imageFormat: Format.Jpeg);
|
_imageSharpManager.ImageCompress(f.FileName, f.OpenReadStream(), thumbnailFilePath);
|
||||||
if(result is null) throw new ArgumentNullException(nameof(result));
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
result = new byte[stream.Length];
|
var result = new byte[stream.Length];
|
||||||
stream.Read(result, 0, result.Length);
|
await stream.ReadAsync(result, 0, result.Length);
|
||||||
}
|
await File.WriteAllBytesAsync(thumbnailFilePath, result);
|
||||||
finally
|
|
||||||
{
|
|
||||||
await System.IO.File.WriteAllBytesAsync(Path.Combine(thumbnailPath, filename), result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<ProjectReference Include="..\..\framework\Yi.Framework.Core\Yi.Framework.Core.csproj" />
|
<ProjectReference Include="..\..\framework\Yi.Framework.Core\Yi.Framework.Core.csproj" />
|
||||||
<ProjectReference Include="..\..\framework\Yi.Framework.Data\Yi.Framework.Data.csproj" />
|
<ProjectReference Include="..\..\framework\Yi.Framework.Data\Yi.Framework.Data.csproj" />
|
||||||
<ProjectReference Include="..\..\framework\Yi.Framework.Ddd\Yi.Framework.Ddd.csproj" />
|
<ProjectReference Include="..\..\framework\Yi.Framework.Ddd\Yi.Framework.Ddd.csproj" />
|
||||||
<ProjectReference Include="..\Yi.Framework.ThumbnailSharp\Yi.Framework.ThumbnailSharp.csproj" />
|
<ProjectReference Include="..\Yi.Framework.ImageSharp\Yi.Framework.ImageSharp.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ using StartupModules;
|
|||||||
using Yi.Framework.Core.Attributes;
|
using Yi.Framework.Core.Attributes;
|
||||||
using Yi.Framework.Core;
|
using Yi.Framework.Core;
|
||||||
using Yi.Framework.Ddd;
|
using Yi.Framework.Ddd;
|
||||||
|
using Yi.Framework.ImageSharp;
|
||||||
|
|
||||||
namespace Yi.Framework.FileManager
|
namespace Yi.Framework.FileManager
|
||||||
{
|
{
|
||||||
[DependsOn(
|
[DependsOn(
|
||||||
typeof(YiFrameworkDddModule)
|
typeof(YiFrameworkDddModule),
|
||||||
|
typeof(YiFrameworkImageSharpModule)
|
||||||
)]
|
)]
|
||||||
public class YiFrameworkFileManagerModule : IStartupModule
|
public class YiFrameworkFileManagerModule : IStartupModule
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
|
||||||
|
namespace Yi.Framework.ImageSharp;
|
||||||
|
public class ImageSharpManager
|
||||||
|
{
|
||||||
|
public void ImageCompress(string fileName, Stream stream, string savePath)
|
||||||
|
{
|
||||||
|
var extensionName = Path.GetExtension(fileName).ToLower();
|
||||||
|
if (extensionName == ".png")
|
||||||
|
{
|
||||||
|
PngImageCompress(stream, savePath);
|
||||||
|
}
|
||||||
|
else if (extensionName == ".jpg" || extensionName == ".jpeg")
|
||||||
|
{
|
||||||
|
JpgImageCompress(stream, savePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
|
||||||
|
{
|
||||||
|
stream.CopyTo(fileStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PngImageCompress(Stream stream, string savePath)
|
||||||
|
{
|
||||||
|
using (var image = Image.Load(stream))
|
||||||
|
{
|
||||||
|
var encoder = new PngEncoder()
|
||||||
|
{
|
||||||
|
CompressionLevel = PngCompressionLevel.Level6,
|
||||||
|
|
||||||
|
};
|
||||||
|
if (image.Width > 300)
|
||||||
|
{
|
||||||
|
image.Mutate(a => a.Resize(image.Width/2, image.Height/2));
|
||||||
|
}
|
||||||
|
|
||||||
|
image.Save(savePath, encoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void JpgImageCompress(Stream stream, string savePath)
|
||||||
|
{
|
||||||
|
using (var image = Image.Load(stream))
|
||||||
|
{
|
||||||
|
var encoder = new JpegEncoder()
|
||||||
|
{
|
||||||
|
Quality = 30
|
||||||
|
};
|
||||||
|
if (image.Width > 300)
|
||||||
|
{
|
||||||
|
image.Mutate(a => a.Resize(image.Width / 2, image.Height / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
image.Save(savePath, encoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -7,9 +7,9 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Yi.Framework.ThumbnailSharp
|
namespace Yi.Framework.ImageSharp
|
||||||
{
|
{
|
||||||
public class YiFrameworkThumbnailSharpModule : IStartupModule
|
public class YiFrameworkImageSharpModule : IStartupModule
|
||||||
{
|
{
|
||||||
public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
|
public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
|
||||||
{
|
{
|
||||||
@@ -17,7 +17,7 @@ namespace Yi.Framework.ThumbnailSharp
|
|||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
|
public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
|
||||||
{
|
{
|
||||||
services.AddSingleton<ThumbnailSharpManager>();
|
services.AddSingleton<ImageSharpManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,410 +0,0 @@
|
|||||||
/*MIT License
|
|
||||||
|
|
||||||
Copyright(c) 2017 Mirza Ghulam Rasyid
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Drawing2D;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Yi.Framework.ThumbnailSharp
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Image format to use when creating a thumbnail.
|
|
||||||
/// </summary>
|
|
||||||
public enum Format
|
|
||||||
{
|
|
||||||
Jpeg,
|
|
||||||
Bmp,
|
|
||||||
Png,
|
|
||||||
Gif,
|
|
||||||
Tiff
|
|
||||||
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Thumbnail class that holds various methods to create an image thumbnail.
|
|
||||||
/// </summary>
|
|
||||||
public class ThumbnailSharpManager
|
|
||||||
{
|
|
||||||
private Bitmap CreateBitmapThumbnail(uint thumbnailSize, string imageFileLocation)
|
|
||||||
{
|
|
||||||
Bitmap bitmap = null;
|
|
||||||
Image image = null;
|
|
||||||
float actualHeight = default(float);
|
|
||||||
float actualWidth = default(float);
|
|
||||||
uint thumbnailHeight = default(uint);
|
|
||||||
uint thumbnailWidth = default(uint);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
image = Image.FromFile(imageFileLocation);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
if (image != null)
|
|
||||||
image = null;
|
|
||||||
}
|
|
||||||
if (image != null)
|
|
||||||
{
|
|
||||||
actualHeight = image.Height;
|
|
||||||
actualWidth = image.Width;
|
|
||||||
if (actualHeight > actualWidth)
|
|
||||||
{
|
|
||||||
if ((uint)actualHeight <= thumbnailSize)
|
|
||||||
throw new Exception("Thumbnail size must be less than actual height (portrait image)");
|
|
||||||
thumbnailHeight = thumbnailSize;
|
|
||||||
thumbnailWidth = (uint)((actualWidth / actualHeight) * thumbnailSize);
|
|
||||||
}
|
|
||||||
else if (actualWidth > actualHeight)
|
|
||||||
{
|
|
||||||
|
|
||||||
if ((uint)actualWidth <= thumbnailSize)
|
|
||||||
throw new Exception("Thumbnail size must be less than actual width (landscape image)");
|
|
||||||
thumbnailWidth = thumbnailSize;
|
|
||||||
thumbnailHeight = (uint)((actualHeight / actualWidth) * thumbnailSize);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((uint)actualWidth <= thumbnailSize)
|
|
||||||
throw new Exception("Thumbnail size must be less than image's size");
|
|
||||||
thumbnailWidth = thumbnailSize;
|
|
||||||
thumbnailHeight = thumbnailSize;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
bitmap = new Bitmap((int)thumbnailWidth, (int)thumbnailHeight);
|
|
||||||
Graphics resizedImage = Graphics.FromImage(bitmap);
|
|
||||||
resizedImage.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
|
||||||
resizedImage.CompositingQuality = CompositingQuality.HighQuality;
|
|
||||||
resizedImage.SmoothingMode = SmoothingMode.HighQuality;
|
|
||||||
resizedImage.DrawImage(image, 0, 0, thumbnailWidth, thumbnailHeight);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
if (bitmap != null)
|
|
||||||
bitmap = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
private Bitmap CreateBitmapThumbnail(uint thumbnailSize, Stream imageStream)
|
|
||||||
{
|
|
||||||
Bitmap bitmap = null;
|
|
||||||
Image image = null;
|
|
||||||
float actualHeight = default(float);
|
|
||||||
float actualWidth = default(float);
|
|
||||||
uint thumbnailHeight = default(uint);
|
|
||||||
uint thumbnailWidth = default(uint);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
image = Image.FromStream(imageStream);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
if (image != null)
|
|
||||||
image = null;
|
|
||||||
}
|
|
||||||
if (image != null)
|
|
||||||
{
|
|
||||||
actualHeight = image.Height;
|
|
||||||
actualWidth = image.Width;
|
|
||||||
if (actualHeight > actualWidth)
|
|
||||||
{
|
|
||||||
if ((uint)actualHeight <= thumbnailSize)
|
|
||||||
throw new Exception("Thumbnail size must be less than actual height (portrait image)");
|
|
||||||
thumbnailHeight = thumbnailSize;
|
|
||||||
thumbnailWidth = (uint)((actualWidth / actualHeight) * thumbnailSize);
|
|
||||||
}
|
|
||||||
else if (actualWidth > actualHeight)
|
|
||||||
{
|
|
||||||
|
|
||||||
if ((uint)actualWidth <= thumbnailSize)
|
|
||||||
throw new Exception("Thumbnail size must be less than actual width (landscape image)");
|
|
||||||
thumbnailWidth = thumbnailSize;
|
|
||||||
thumbnailHeight = (uint)((actualHeight / actualWidth) * thumbnailSize);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((uint)actualWidth <= thumbnailSize)
|
|
||||||
throw new Exception("Thumbnail size must be less than image's size");
|
|
||||||
thumbnailWidth = thumbnailSize;
|
|
||||||
thumbnailHeight = thumbnailSize;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bitmap = new Bitmap((int)thumbnailWidth, (int)thumbnailHeight);
|
|
||||||
Graphics resizedImage = Graphics.FromImage(bitmap);
|
|
||||||
resizedImage.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
|
||||||
resizedImage.CompositingQuality = CompositingQuality.HighQuality;
|
|
||||||
resizedImage.SmoothingMode = SmoothingMode.HighQuality;
|
|
||||||
resizedImage.DrawImage(image, 0, 0, thumbnailWidth, thumbnailHeight);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
if (bitmap != null)
|
|
||||||
bitmap = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
private ImageFormat GetImageFormat(Format format)
|
|
||||||
{
|
|
||||||
switch (format)
|
|
||||||
{
|
|
||||||
case Format.Jpeg:
|
|
||||||
return ImageFormat.Jpeg;
|
|
||||||
case Format.Bmp:
|
|
||||||
return ImageFormat.Bmp;
|
|
||||||
case Format.Png:
|
|
||||||
return ImageFormat.Png;
|
|
||||||
case Format.Gif:
|
|
||||||
return ImageFormat.Gif;
|
|
||||||
default:
|
|
||||||
return ImageFormat.Tiff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private async Task<Stream> GetImageStreamFromUrl(Uri urlAddress)
|
|
||||||
{
|
|
||||||
Stream result = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
byte[] bytes = await GetImageBytesFromUrl(urlAddress);
|
|
||||||
result = new MemoryStream(bytes);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
private async Task<byte[]> GetImageBytesFromUrl(Uri urlAddress)
|
|
||||||
{
|
|
||||||
byte[] buffer = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (HttpClient client = new HttpClient())
|
|
||||||
{
|
|
||||||
buffer = await client.GetByteArrayAsync(urlAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
buffer = null;
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a thumbnail from file and returns as stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thumbnailSize">Thumbnail size. For portrait image, thumbnail size must be less than its height.
|
|
||||||
/// For landscape image, thumbnail size must be less than its width. For the same size image (Proportional), thumbnail size must be less than its width and height.</param>
|
|
||||||
/// <param name="imageFileLocation">Correct image file location.</param>
|
|
||||||
/// <param name="imageFormat">Image format to use.</param>
|
|
||||||
/// <returns>A thumbnail image as stream. Returns null if it fails.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">'imageFileLocation' is null.</exception>
|
|
||||||
/// <exception cref="FileNotFoundException">'imageFileLocation' does not exist.</exception>
|
|
||||||
public Stream CreateThumbnailStream(uint thumbnailSize, string imageFileLocation, Format imageFormat)
|
|
||||||
{
|
|
||||||
if (String.IsNullOrEmpty(imageFileLocation))
|
|
||||||
throw new ArgumentNullException(nameof(imageFileLocation), "'imageFileLocation' cannot be null");
|
|
||||||
if (!File.Exists(imageFileLocation))
|
|
||||||
throw new FileNotFoundException($"'{imageFileLocation}' cannot be found");
|
|
||||||
Bitmap bitmap = CreateBitmapThumbnail(thumbnailSize, imageFileLocation);
|
|
||||||
if (bitmap != null)
|
|
||||||
{
|
|
||||||
MemoryStream stream = new MemoryStream();
|
|
||||||
bitmap.Save(stream, GetImageFormat(imageFormat));
|
|
||||||
stream.Position = 0;
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Create a thumbnail from image stream and returns as stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thumbnailSize">Thumbnail size. For portrait image, thumbnail size must be less than its height.
|
|
||||||
/// For landscape image, thumbnail size must be less than its width. For the same size image (Proportional), thumbnail size must be less than its width and height.</param>
|
|
||||||
/// <param name="imageStream">Valid image stream object.</param>
|
|
||||||
/// <param name="imageFormat">Image format to use.</param>
|
|
||||||
/// <returns>A thumbnail image as stream. Returns null if it fails.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">'imageStream' is null.</exception>
|
|
||||||
public Stream CreateThumbnailStream(uint thumbnailSize, Stream imageStream, Format imageFormat)
|
|
||||||
{
|
|
||||||
if (imageStream == null)
|
|
||||||
throw new ArgumentNullException(nameof(imageStream), "'imageStream' cannot be null");
|
|
||||||
Bitmap bitmap = CreateBitmapThumbnail(thumbnailSize, imageStream);
|
|
||||||
if (bitmap != null)
|
|
||||||
{
|
|
||||||
MemoryStream stream = new MemoryStream();
|
|
||||||
bitmap.Save(stream, GetImageFormat(imageFormat));
|
|
||||||
stream.Position = 0;
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Create a thumbnail from image in bytes and returns as stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thumbnailSize">Thumbnail size. For portrait image, thumbnail size must be less than its height.
|
|
||||||
/// For landscape image, thumbnail size must be less than its width. For the same size image (Proportional), thumbnail size must be less than its width and height.</param>
|
|
||||||
/// <param name="imageBytes">Valid image bytes array.</param>
|
|
||||||
/// <param name="imageFormat">Image format to use.</param>
|
|
||||||
/// <returns>A thumbnail image as stream. Returns null if it fails.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">'imageBytes' is null.</exception>
|
|
||||||
public Stream CreateThumbnailStream(uint thumbnailSize, byte[] imageBytes, Format imageFormat)
|
|
||||||
{
|
|
||||||
if (imageBytes == null)
|
|
||||||
throw new ArgumentNullException(nameof(imageBytes), "'imageStream' cannot be null");
|
|
||||||
Bitmap bitmap = CreateBitmapThumbnail(thumbnailSize, new MemoryStream(imageBytes));
|
|
||||||
if (bitmap != null)
|
|
||||||
{
|
|
||||||
MemoryStream stream = new MemoryStream();
|
|
||||||
bitmap.Save(stream, GetImageFormat(imageFormat));
|
|
||||||
stream.Position = 0;
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Create a thumbnail from file and returns as bytes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thumbnailSize">Thumbnail size. For portrait image, thumbnail size must be less than its height.
|
|
||||||
/// For landscape image, thumbnail size must be less than its width. For the same size image (Proportional), thumbnail size must be less than its width and height.</param>
|
|
||||||
/// <param name="imageFileLocation">Correct image file location.</param>
|
|
||||||
/// <param name="imageFormat">Image format to use.</param>
|
|
||||||
/// <returns>A thumbnail image as bytes. Returns null if it fails.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">'imageFileLocation' is null.</exception>
|
|
||||||
/// <exception cref="FileNotFoundException">'imageFileLocation' does not exist.</exception>
|
|
||||||
public byte[] CreateThumbnailBytes(uint thumbnailSize, string imageFileLocation, Format imageFormat)
|
|
||||||
{
|
|
||||||
if (String.IsNullOrEmpty(imageFileLocation))
|
|
||||||
throw new ArgumentNullException(nameof(imageFileLocation), "'imageFileLocation' cannot be null");
|
|
||||||
if (!File.Exists(imageFileLocation))
|
|
||||||
throw new FileNotFoundException($"'{imageFileLocation}' cannot be found");
|
|
||||||
Stream stream = CreateThumbnailStream(thumbnailSize, imageFileLocation, imageFormat);
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
byte[] streamBytes = new byte[stream.Length];
|
|
||||||
stream.Read(streamBytes, 0, streamBytes.Length);
|
|
||||||
return streamBytes;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Create a thumbnail from image stream and returns as bytes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thumbnailSize">Thumbnail size. For portrait image, thumbnail size must be less than its height.
|
|
||||||
/// For landscape image, thumbnail size must be less than its width. For the same size image (Proportional), thumbnail size must be less than its width and height.</param>
|
|
||||||
/// <param name="imageStream">Valid image stream object.</param>
|
|
||||||
/// <param name="imageFormat">Image format to use.</param>
|
|
||||||
/// <returns>A thumbnail image as bytes. Returns null if it fails.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">'imageStream' is null.</exception>
|
|
||||||
public byte[] CreateThumbnailBytes(uint thumbnailSize, Stream imageStream, Format imageFormat)
|
|
||||||
{
|
|
||||||
if (imageStream == null)
|
|
||||||
throw new ArgumentNullException(nameof(imageStream), "'imageStream' cannot be null");
|
|
||||||
|
|
||||||
Stream stream = CreateThumbnailStream(thumbnailSize, imageStream, imageFormat);
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
byte[] streamBytes = new byte[stream.Length];
|
|
||||||
stream.Read(streamBytes, 0, streamBytes.Length);
|
|
||||||
return streamBytes;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Create a thumbnail from image in bytes and returns as bytes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thumbnailSize">Thumbnail size. For portrait image, thumbnail size must be less than its height.
|
|
||||||
/// For landscape image, thumbnail size must be less than its width. For the same size image (Proportional), thumbnail size must be less than its width and height.</param>
|
|
||||||
/// <param name="imageBytes">Valid image bytes array.</param>
|
|
||||||
/// <param name="imageFormat">Image format to use.</param>
|
|
||||||
/// <returns>A thumbnail image as bytes. Returns null if it fails.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">'imageBytes' is null.</exception>
|
|
||||||
public byte[] CreateThumbnailBytes(uint thumbnailSize, byte[] imageBytes, Format imageFormat)
|
|
||||||
{
|
|
||||||
if (imageBytes == null)
|
|
||||||
throw new ArgumentNullException(nameof(imageBytes), "'imageStream' cannot be null");
|
|
||||||
Stream stream = CreateThumbnailStream(thumbnailSize, imageBytes, imageFormat);
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
byte[] streamBytes = new byte[stream.Length];
|
|
||||||
stream.Read(streamBytes, 0, streamBytes.Length);
|
|
||||||
return streamBytes;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a thumbnail from valid image url asynchronously.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thumbnailSize">Thumbnail size. For portrait image, thumbnail size must be less than its height.
|
|
||||||
/// For landscape image, thumbnail size must be less than its width. For the same size image (Proportional), thumbnail size must be less than its width and height.</param>
|
|
||||||
/// <param name="urlAddress">Valid absolute url address with proper scheme.</param>
|
|
||||||
/// <param name="imageFormat">Image format to use.</param>
|
|
||||||
/// <returns>A thumbnail image as stream. Returns null if it fails.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">'urlAddress' is null.</exception>
|
|
||||||
public async Task<Stream> CreateThumbnailStreamAsync(uint thumbnailSize, Uri urlAddress, Format imageFormat)
|
|
||||||
{
|
|
||||||
if (urlAddress == null)
|
|
||||||
throw new ArgumentNullException(nameof(urlAddress), "'urlAddress' cannot be null");
|
|
||||||
Stream result = null;
|
|
||||||
Stream stream = await GetImageStreamFromUrl(urlAddress);
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
result = CreateThumbnailStream(thumbnailSize, stream, imageFormat);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a thumbnail from valid image url asynchronously.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thumbnailSize">Thumbnail size. For portrait image, thumbnail size must be less than its height.
|
|
||||||
/// For landscape image, thumbnail size must be less than its width. For the same size image (Proportional), thumbnail size must be less than its width and height.</param>
|
|
||||||
/// <param name="urlAddress">Valid absolute url address with proper scheme.</param>
|
|
||||||
/// <param name="imageFormat">Image format to use.</param>
|
|
||||||
/// <returns>A thumbnail image as bytes. Returns null if it fails.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">'urlAddress' is null.</exception>
|
|
||||||
public async Task<byte[]> CreateThumbnailBytesAsync(uint thumbnailSize, Uri urlAddress, Format imageFormat)
|
|
||||||
{
|
|
||||||
if (urlAddress == null)
|
|
||||||
throw new ArgumentNullException(nameof(urlAddress), "'urlAddress' cannot be null");
|
|
||||||
byte[] result = null;
|
|
||||||
byte[] imageBytes = await GetImageBytesFromUrl(urlAddress);
|
|
||||||
if (imageBytes != null)
|
|
||||||
{
|
|
||||||
result = CreateThumbnailBytes(thumbnailSize, imageBytes, imageFormat);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 7.9 MiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 451 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |