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