mirror of https://github.com/SixLabors/ImageSharp
8 changed files with 440 additions and 2 deletions
@ -0,0 +1,221 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using System.Drawing.Imaging; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices; |
|||
using FreeImageAPI; |
|||
using ImageMagick; |
|||
using PhotoSauce.MagicScaler; |
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests; |
|||
using SkiaSharp; |
|||
using ImageSharpImage = SixLabors.ImageSharp.Image; |
|||
using ImageSharpSize = SixLabors.ImageSharp.Size; |
|||
using NetVipsImage = NetVips.Image; |
|||
using SystemDrawingImage = System.Drawing.Image; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave |
|||
{ |
|||
public class LoadResizeSaveStressRunner |
|||
{ |
|||
private const int ThumbnailSize = 150; |
|||
private const int Quality = 75; |
|||
private const string ImageSharp = nameof(ImageSharp); |
|||
private const string SystemDrawing = nameof(SystemDrawing); |
|||
private const string MagickNET = nameof(MagickNET); |
|||
private const string NetVips = nameof(NetVips); |
|||
private const string FreeImage = nameof(FreeImage); |
|||
private const string MagicScaler = nameof(MagicScaler); |
|||
private const string SkiaSharpCanvas = nameof(SkiaSharpCanvas); |
|||
private const string SkiaSharpBitmap = nameof(SkiaSharpBitmap); |
|||
|
|||
// Set the quality for ImagSharp
|
|||
private readonly JpegEncoder imageSharpJpegEncoder = new () { Quality = Quality }; |
|||
private readonly ImageCodecInfo systemDrawingJpegCodec = |
|||
ImageCodecInfo.GetImageEncoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid); |
|||
|
|||
public string[] Images { get; private set; } |
|||
|
|||
private string outputDirectory; |
|||
|
|||
public int ImageCount { get; set; } = int.MaxValue; |
|||
|
|||
public void Init() |
|||
{ |
|||
if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) |
|||
{ |
|||
// Workaround ImageMagick issue
|
|||
OpenCL.IsEnabled = false; |
|||
} |
|||
|
|||
string imageDirectory = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "MemoryStress"); |
|||
if (!Directory.Exists(imageDirectory) || !Directory.EnumerateFiles(imageDirectory).Any()) |
|||
{ |
|||
throw new DirectoryNotFoundException($"Copy stress images to: {imageDirectory}"); |
|||
} |
|||
|
|||
// Get at most this.ImageCount images from there
|
|||
this.Images = Directory.EnumerateFiles(imageDirectory).Take(this.ImageCount).ToArray(); |
|||
|
|||
// Create the output directory next to the images directory
|
|||
this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); |
|||
} |
|||
|
|||
private string OutputPath(string inputPath, string postfix) => |
|||
Path.Combine( |
|||
this.outputDirectory, |
|||
Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); |
|||
|
|||
private (int width, int height) ScaledSize(int inWidth, int inHeight, int outSize) |
|||
{ |
|||
int width, height; |
|||
if (inWidth > inHeight) |
|||
{ |
|||
width = outSize; |
|||
height = (int)Math.Round(inHeight * outSize / (double)inWidth); |
|||
} |
|||
else |
|||
{ |
|||
width = (int)Math.Round(inWidth * outSize / (double)inHeight); |
|||
height = outSize; |
|||
} |
|||
|
|||
return (width, height); |
|||
} |
|||
|
|||
public void SystemDrawingResize(string input) |
|||
{ |
|||
using var image = SystemDrawingImage.FromFile(input, true); |
|||
(int width, int height) scaled = this.ScaledSize(image.Width, image.Height, ThumbnailSize); |
|||
var resized = new Bitmap(scaled.width, scaled.height); |
|||
using var graphics = Graphics.FromImage(resized); |
|||
using var attributes = new ImageAttributes(); |
|||
attributes.SetWrapMode(WrapMode.TileFlipXY); |
|||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; |
|||
graphics.CompositingMode = CompositingMode.SourceCopy; |
|||
graphics.CompositingQuality = CompositingQuality.AssumeLinear; |
|||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; |
|||
graphics.DrawImage(image, System.Drawing.Rectangle.FromLTRB(0, 0, resized.Width, resized.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); |
|||
|
|||
// Save the results
|
|||
using var encoderParams = new EncoderParameters(1); |
|||
using var qualityParam = new EncoderParameter(Encoder.Quality, (long)Quality); |
|||
encoderParams.Param[0] = qualityParam; |
|||
resized.Save(this.OutputPath(input, SystemDrawing), this.systemDrawingJpegCodec, encoderParams); |
|||
} |
|||
|
|||
public void ImageSharpResize(string input) |
|||
{ |
|||
using FileStream output = File.Open(this.OutputPath(input, ImageSharp), FileMode.Create); |
|||
|
|||
// Resize it to fit a 150x150 square
|
|||
using var image = ImageSharpImage.Load(input); |
|||
image.Mutate(i => i.Resize(new ResizeOptions |
|||
{ |
|||
Size = new ImageSharpSize(ThumbnailSize, ThumbnailSize), |
|||
Mode = ResizeMode.Max |
|||
})); |
|||
|
|||
// Reduce the size of the file
|
|||
image.Metadata.ExifProfile = null; |
|||
|
|||
// Save the results
|
|||
image.Save(output, this.imageSharpJpegEncoder); |
|||
} |
|||
|
|||
public void MagickResize(string input) |
|||
{ |
|||
using var image = new MagickImage(input); |
|||
|
|||
// Resize it to fit a 150x150 square
|
|||
image.Resize(ThumbnailSize, ThumbnailSize); |
|||
|
|||
// Reduce the size of the file
|
|||
image.Strip(); |
|||
|
|||
// Set the quality
|
|||
image.Quality = Quality; |
|||
|
|||
// Save the results
|
|||
image.Write(this.OutputPath(input, MagickNET)); |
|||
} |
|||
|
|||
public void FreeImageResize(string input) |
|||
{ |
|||
using var original = FreeImageBitmap.FromFile(input); |
|||
(int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); |
|||
var resized = new FreeImageBitmap(original, scaled.width, scaled.height); |
|||
|
|||
// JPEG_QUALITYGOOD is 75 JPEG.
|
|||
// JPEG_BASELINE strips metadata (EXIF, etc.)
|
|||
resized.Save( |
|||
this.OutputPath(input, FreeImage), |
|||
FREE_IMAGE_FORMAT.FIF_JPEG, |
|||
FREE_IMAGE_SAVE_FLAGS.JPEG_QUALITYGOOD | FREE_IMAGE_SAVE_FLAGS.JPEG_BASELINE); |
|||
} |
|||
|
|||
public void MagicScalerResize(string input) |
|||
{ |
|||
var settings = new ProcessImageSettings() |
|||
{ |
|||
Width = ThumbnailSize, |
|||
Height = ThumbnailSize, |
|||
ResizeMode = CropScaleMode.Max, |
|||
SaveFormat = FileFormat.Jpeg, |
|||
JpegQuality = Quality, |
|||
JpegSubsampleMode = ChromaSubsampleMode.Subsample420 |
|||
}; |
|||
|
|||
using var output = new FileStream(this.OutputPath(input, MagicScaler), FileMode.Create); |
|||
MagicImageProcessor.ProcessImage(input, output, settings); |
|||
} |
|||
|
|||
public void SkiaCanvasResize(string input) |
|||
{ |
|||
using var original = SKBitmap.Decode(input); |
|||
(int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); |
|||
using var surface = SKSurface.Create(new SKImageInfo(scaled.width, scaled.height, original.ColorType, original.AlphaType)); |
|||
using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; |
|||
SKCanvas canvas = surface.Canvas; |
|||
canvas.Scale((float)scaled.width / original.Width); |
|||
canvas.DrawBitmap(original, 0, 0, paint); |
|||
canvas.Flush(); |
|||
|
|||
using FileStream output = File.OpenWrite(this.OutputPath(input, SkiaSharpCanvas)); |
|||
surface.Snapshot() |
|||
.Encode(SKEncodedImageFormat.Jpeg, Quality) |
|||
.SaveTo(output); |
|||
} |
|||
|
|||
public void SkiaBitmapResize(string input) |
|||
{ |
|||
using var original = SKBitmap.Decode(input); |
|||
(int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); |
|||
using var resized = original.Resize(new SKImageInfo(scaled.width, scaled.height), SKFilterQuality.High); |
|||
if (resized == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
using var image = SKImage.FromBitmap(resized); |
|||
using FileStream output = File.OpenWrite(this.OutputPath(input, SkiaSharpBitmap)); |
|||
image.Encode(SKEncodedImageFormat.Jpeg, Quality) |
|||
.SaveTo(output); |
|||
} |
|||
|
|||
public void NetVipsResize(string input) |
|||
{ |
|||
// Thumbnail to fit a 150x150 square
|
|||
using var thumb = NetVipsImage.Thumbnail(input, ThumbnailSize, ThumbnailSize); |
|||
|
|||
// Save the results
|
|||
thumb.Jpegsave(this.OutputPath(input, NetVips), q: Quality, strip: true); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave |
|||
{ |
|||
public class LoadResizeSaveStress_NonParallel |
|||
{ |
|||
private LoadResizeSaveStressRunner benchmarks; |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; |
|||
this.benchmarks.Init(); |
|||
} |
|||
|
|||
private void ForEachImage(Action<string> action) |
|||
{ |
|||
foreach (string image in this.benchmarks.Images) |
|||
{ |
|||
action(image); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save")] |
|||
public void SystemDrawingBenchmark() => this.ForEachImage(this.benchmarks.SystemDrawingResize); |
|||
|
|||
[Benchmark(Description = "ImageSharp Load, Resize, Save")] |
|||
public void ImageSharpBenchmark() => this.ForEachImage(this.benchmarks.ImageSharpResize); |
|||
|
|||
[Benchmark(Description = "ImageMagick Load, Resize, Save")] |
|||
public void MagickBenchmark() => this.ForEachImage(this.benchmarks.MagickResize); |
|||
|
|||
[Benchmark(Description = "ImageFree Load, Resize, Save")] |
|||
public void FreeImageBenchmark() => this.ForEachImage(this.benchmarks.FreeImageResize); |
|||
|
|||
[Benchmark(Description = "MagicScaler Load, Resize, Save")] |
|||
public void MagicScalerBenchmark() => this.ForEachImage(this.benchmarks.MagicScalerResize); |
|||
|
|||
[Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save")] |
|||
public void SkiaCanvasBenchmark() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); |
|||
|
|||
[Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save")] |
|||
public void SkiaBitmapBenchmark() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); |
|||
|
|||
[Benchmark(Description = "NetVips Load, Resize, Save")] |
|||
public void NetVipsBenchmark() => this.ForEachImage(this.benchmarks.NetVipsResize); |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave |
|||
{ |
|||
[MemoryDiagnoser] |
|||
public class LoadResizeSaveStress_Parallel |
|||
{ |
|||
private LoadResizeSaveStressRunner benchmarks; |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; |
|||
this.benchmarks.Init(); |
|||
} |
|||
|
|||
private void ForEachImage(Action<string> action) => Parallel.ForEach(this.benchmarks.Images, action); |
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save - Parallel")] |
|||
public void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); |
|||
|
|||
[Benchmark(Description = "ImageSharp Load, Resize, Save - Parallel")] |
|||
public void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); |
|||
|
|||
[Benchmark(Description = "ImageMagick Load, Resize, Save - Parallel")] |
|||
public void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); |
|||
|
|||
[Benchmark(Description = "ImageFree Load, Resize, Save - Parallel")] |
|||
public void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); |
|||
|
|||
[Benchmark(Description = "MagicScaler Load, Resize, Save - Parallel")] |
|||
public void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); |
|||
|
|||
[Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save - Parallel")] |
|||
public void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); |
|||
|
|||
[Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save - Parallel")] |
|||
public void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); |
|||
|
|||
[Benchmark(Description = "NetVips Load, Resize, Save - Parallel")] |
|||
public void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.ProfilingSandbox |
|||
{ |
|||
internal class LoadResizeSaveParallelMemoryStress |
|||
{ |
|||
private readonly LoadResizeSaveStressRunner benchmarks; |
|||
|
|||
public LoadResizeSaveParallelMemoryStress() |
|||
{ |
|||
this.benchmarks = new LoadResizeSaveStressRunner(); |
|||
this.benchmarks.Init(); |
|||
} |
|||
|
|||
public static void Run() |
|||
{ |
|||
Console.WriteLine(@"Choose a library for image resizing stress test:
|
|||
|
|||
1. System.Drawing |
|||
2. ImageSharp |
|||
3. MagicScaler |
|||
4. SkiaSharp |
|||
5. NetVips |
|||
6. ImageMagick |
|||
7. FreeImage |
|||
");
|
|||
|
|||
ConsoleKey key = Console.ReadKey().Key; |
|||
if (key < ConsoleKey.D1 || key > ConsoleKey.D7) |
|||
{ |
|||
Console.WriteLine("Unrecognized command."); |
|||
return; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var lrs = new LoadResizeSaveParallelMemoryStress(); |
|||
Console.WriteLine("\nRunning..."); |
|||
var timer = new Stopwatch(); |
|||
timer.Start(); |
|||
|
|||
switch (key) |
|||
{ |
|||
case ConsoleKey.D1: |
|||
lrs.SystemDrawingBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D2: |
|||
lrs.ImageSharpBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D3: |
|||
lrs.MagicScalerBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D4: |
|||
lrs.SkiaCanvasBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D5: |
|||
lrs.NetVipsBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D6: |
|||
lrs.MagickBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D7: |
|||
lrs.FreeImageBenchmarkParallel(); |
|||
break; |
|||
} |
|||
|
|||
timer.Stop(); |
|||
Console.WriteLine($"Completed in {timer.ElapsedMilliseconds / 1000.0:f3}sec"); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Console.WriteLine(ex.ToString()); |
|||
} |
|||
} |
|||
|
|||
private void ForEachImage(Action<string> action) => Parallel.ForEach(this.benchmarks.Images, action); |
|||
|
|||
private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); |
|||
|
|||
private void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); |
|||
|
|||
private void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); |
|||
|
|||
private void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); |
|||
|
|||
private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); |
|||
|
|||
private void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); |
|||
|
|||
private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); |
|||
|
|||
private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue