mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
10 changed files with 529 additions and 9 deletions
@ -0,0 +1,69 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave |
|||
{ |
|||
// See README.md for instructions about initialization.
|
|||
[MemoryDiagnoser] |
|||
[ShortRunJob] |
|||
public class LoadResizeSaveStressBenchmarks |
|||
{ |
|||
private LoadResizeSaveStressRunner runner; |
|||
|
|||
// private const JpegKind Filter = JpegKind.Progressive;
|
|||
private const JpegKind Filter = JpegKind.Any; |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.runner = new LoadResizeSaveStressRunner() |
|||
{ |
|||
ImageCount = Environment.ProcessorCount, |
|||
Filter = Filter |
|||
}; |
|||
Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}"); |
|||
this.runner.Init(); |
|||
} |
|||
|
|||
private void ForEachImage(Action<string> action, int maxDegreeOfParallelism) |
|||
{ |
|||
this.runner.MaxDegreeOfParallelism = maxDegreeOfParallelism; |
|||
this.runner.ForEachImageParallel(action); |
|||
} |
|||
|
|||
public int[] ParallelismValues { get; } = |
|||
{ |
|||
Environment.ProcessorCount, |
|||
Environment.ProcessorCount / 2, |
|||
Environment.ProcessorCount / 4, |
|||
1 |
|||
}; |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
[ArgumentsSource(nameof(ParallelismValues))] |
|||
public void SystemDrawing(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SystemDrawingResize, maxDegreeOfParallelism); |
|||
|
|||
[Benchmark] |
|||
[ArgumentsSource(nameof(ParallelismValues))] |
|||
public void ImageSharp(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); |
|||
|
|||
[Benchmark] |
|||
[ArgumentsSource(nameof(ParallelismValues))] |
|||
public void Magick(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagickResize, maxDegreeOfParallelism); |
|||
|
|||
[Benchmark] |
|||
[ArgumentsSource(nameof(ParallelismValues))] |
|||
public void MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, maxDegreeOfParallelism); |
|||
|
|||
[Benchmark] |
|||
[ArgumentsSource(nameof(ParallelismValues))] |
|||
public void SkiaBitmap(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapResize, maxDegreeOfParallelism); |
|||
|
|||
[Benchmark] |
|||
[ArgumentsSource(nameof(ParallelismValues))] |
|||
public void NetVips(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.NetVipsResize, maxDegreeOfParallelism); |
|||
} |
|||
} |
|||
@ -0,0 +1,280 @@ |
|||
// 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 System.Threading.Tasks; |
|||
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 enum JpegKind |
|||
{ |
|||
Baseline = 1, |
|||
Progressive = 2, |
|||
Any = Baseline | Progressive |
|||
} |
|||
|
|||
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 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; } |
|||
|
|||
public double TotalProcessedMegapixels { get; private set; } |
|||
|
|||
private string outputDirectory; |
|||
|
|||
public int ImageCount { get; set; } = int.MaxValue; |
|||
|
|||
public int MaxDegreeOfParallelism { get; set; } = -1; |
|||
|
|||
public JpegKind Filter { get; set; } |
|||
|
|||
private static readonly string[] ProgressiveFiles = |
|||
{ |
|||
"ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg", |
|||
"acanthopus-excellens-f-face-brasil_2014-08-06-132105-zs-pmax_14792513890_o.jpg", |
|||
"bee-ceratina-monster-f-ukraine-face_2014-08-09-123342-zs-pmax_15068816101_o.jpg", |
|||
"bombus-eximias-f-tawain-face_2014-08-10-094449-zs-pmax_15155452565_o.jpg", |
|||
"ceratina-14507h1-m-vietnam-face_2014-08-09-163218-zs-pmax_15096718245_o.jpg", |
|||
"ceratina-buscki-f-panama-face_2014-11-25-140413-zs-pmax_15923736081_o.jpg", |
|||
"ceratina-tricolor-f-panama-face2_2014-08-29-160402-zs-pmax_14906318297_o.jpg", |
|||
"ceratina-tricolor-f-panama-face_2014-08-29-160001-zs-pmax_14906300608_o.jpg", |
|||
"ceratina-tricolor-m-panama-face_2014-08-29-162821-zs-pmax_15069878876_o.jpg", |
|||
"coelioxys-cayennensis-f-argentina-face_2014-08-09-171932-zs-pmax_14914109737_o.jpg", |
|||
"ctenocolletes-smaragdinus-f-australia-face_2014-08-08-134825-zs-pmax_14865269708_o.jpg", |
|||
"diphaglossa-gayi-f-face-chile_2014-08-04-180547-zs-pmax_14918891472_o.jpg", |
|||
"hylaeus-nubilosus-f-australia-face_2014-08-14-121100-zs-pmax_15049602149_o.jpg", |
|||
"hypanthidioides-arenaria-f-face-brazil_2014-08-06-061201-zs-pmax_14770371360_o.jpg", |
|||
"megachile-chalicodoma-species-f-morocco-face_2014-08-14-124840-zs-pmax_15217084686_o.jpg", |
|||
"megachile-species-f-15266b06-face-kenya_2014-08-06-161044-zs-pmax_14994381392_o.jpg", |
|||
"megalopta-genalis-m-face-panama-barocolorado_2014-09-19-164939-zs-pmax_15121397069_o.jpg", |
|||
"melitta-haemorrhoidalis-m--england-face_2014-11-02-014026-zs-pmax-recovered_15782113675_o.jpg", |
|||
"nomia-heart-antennae-m-15266b02-face-kenya_2014-08-04-195216-zs-pmax_14922843736_o.jpg", |
|||
"nomia-species-m-oman-face_2014-08-09-192602-zs-pmax_15128732411_o.jpg", |
|||
"nomia-spiney-m-vietnam-face_2014-08-09-213126-zs-pmax_15191389705_o.jpg", |
|||
"ochreriades-fasciata-m-face-israel_2014-08-06-084407-zs-pmax_14965515571_o.jpg", |
|||
"osmia-brevicornisf-jaw-kyrgystan_2014-08-08-103333-zs-pmax_14865267787_o.jpg", |
|||
"pachyanthidium-aff-benguelense-f-6711f07-face_2014-08-07-112830-zs-pmax_15018069042_o.jpg", |
|||
"pachymelus-bicolor-m-face-madagascar_2014-08-06-134930-zs-pmax_14801667477_o.jpg", |
|||
"psaenythia-species-m-argentina-face_2014-08-07-163754-zs-pmax_15007018976_o.jpg", |
|||
"stingless-bee-1-f-face-peru_2014-07-30-123322-zs-pmax_15633797167_o.jpg", |
|||
"triepeolus-simplex-m-face-md-kent-county_2014-07-22-100937-zs-pmax_14805405233_o.jpg", |
|||
"washed-megachile-f-face-chile_2014-08-06-103414-zs-pmax_14977843152_o.jpg", |
|||
"xylocopa-balck-violetwing-f-kyrgystan-angle_2014-08-09-182433-zs-pmax_15123416061_o.jpg", |
|||
"xylocopa-india-yellow-m-india-face_2014-08-10-111701-zs-pmax_15166559172_o.jpg", |
|||
}; |
|||
|
|||
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
|
|||
bool FilterFunc(string f) => this.Filter.HasFlag(GetJpegType(f)); |
|||
|
|||
this.Images = Directory.EnumerateFiles(imageDirectory).Where(FilterFunc).Take(this.ImageCount).ToArray(); |
|||
|
|||
// Create the output directory next to the images directory
|
|||
this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); |
|||
|
|||
static JpegKind GetJpegType(string f) => |
|||
ProgressiveFiles.Any(p => f.EndsWith(p, StringComparison.OrdinalIgnoreCase)) |
|||
? JpegKind.Progressive |
|||
: JpegKind.Baseline; |
|||
} |
|||
|
|||
public void ForEachImageParallel(Action<string> action) => Parallel.ForEach( |
|||
this.Images, |
|||
new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, |
|||
action); |
|||
|
|||
private void IncreaseTotalMegapixels(int width, int height) |
|||
{ |
|||
double pixels = width * (double)height; |
|||
this.TotalProcessedMegapixels += pixels / 1_000_000.0; |
|||
} |
|||
|
|||
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); |
|||
this.IncreaseTotalMegapixels(image.Width, image.Height); |
|||
|
|||
(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); |
|||
this.IncreaseTotalMegapixels(image.Width, image.Height); |
|||
|
|||
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); |
|||
this.IncreaseTotalMegapixels(image.Width, image.Height); |
|||
|
|||
// 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 MagicScalerResize(string input) |
|||
{ |
|||
var settings = new ProcessImageSettings() |
|||
{ |
|||
Width = ThumbnailSize, |
|||
Height = ThumbnailSize, |
|||
ResizeMode = CropScaleMode.Max, |
|||
SaveFormat = FileFormat.Jpeg, |
|||
JpegQuality = Quality, |
|||
JpegSubsampleMode = ChromaSubsampleMode.Subsample420 |
|||
}; |
|||
|
|||
// TODO: Is there a way to capture input dimensions for IncreaseTotalMegapixels?
|
|||
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); |
|||
this.IncreaseTotalMegapixels(original.Width, original.Height); |
|||
(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); |
|||
this.IncreaseTotalMegapixels(original.Width, original.Height); |
|||
(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,9 @@ |
|||
The benchmarks have been adapted from the |
|||
[PhotoSauce's MemoryStress project](https://github.com/saucecontrol/core-imaging-playground/tree/beeees/MemoryStress). |
|||
|
|||
### Setup |
|||
|
|||
Download the [Bee Heads album](https://www.flickr.com/photos/usgsbiml/albums/72157633925491877) from the USGS Bee Inventory flickr |
|||
and extract to folder `<solution-dir>\tests\Images\ActualOutput\MemoryStress\`. |
|||
|
|||
|
|||
@ -0,0 +1,142 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Text; |
|||
using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.ProfilingSandbox |
|||
{ |
|||
// See ImageSharp.Benchmarks/LoadResizeSave/README.md
|
|||
internal class LoadResizeSaveParallelMemoryStress |
|||
{ |
|||
private readonly LoadResizeSaveStressRunner benchmarks; |
|||
|
|||
private LoadResizeSaveParallelMemoryStress() |
|||
{ |
|||
this.benchmarks = new LoadResizeSaveStressRunner() |
|||
{ |
|||
// MaxDegreeOfParallelism = 10,
|
|||
// Filter = JpegKind.Baseline
|
|||
}; |
|||
this.benchmarks.Init(); |
|||
} |
|||
|
|||
private double TotalProcessedMegapixels => this.benchmarks.TotalProcessedMegapixels; |
|||
|
|||
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 |
|||
");
|
|||
|
|||
ConsoleKey key = Console.ReadKey().Key; |
|||
if (key < ConsoleKey.D1 || key > ConsoleKey.D6) |
|||
{ |
|||
Console.WriteLine("Unrecognized command."); |
|||
return; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var lrs = new LoadResizeSaveParallelMemoryStress(); |
|||
|
|||
Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); |
|||
Console.WriteLine($"Running with MaxDegreeOfParallelism={lrs.benchmarks.MaxDegreeOfParallelism} ..."); |
|||
var timer = Stopwatch.StartNew(); |
|||
|
|||
switch (key) |
|||
{ |
|||
case ConsoleKey.D1: |
|||
lrs.SystemDrawingBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D2: |
|||
lrs.ImageSharpBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D3: |
|||
lrs.MagicScalerBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D4: |
|||
lrs.SkiaBitmapBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D5: |
|||
lrs.NetVipsBenchmarkParallel(); |
|||
break; |
|||
case ConsoleKey.D6: |
|||
lrs.MagickBenchmarkParallel(); |
|||
break; |
|||
} |
|||
|
|||
timer.Stop(); |
|||
var stats = new Stats(timer, lrs.TotalProcessedMegapixels); |
|||
Console.WriteLine("Done. TotalProcessedMegapixels: " + lrs.TotalProcessedMegapixels); |
|||
Console.WriteLine(stats.GetMarkdown()); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Console.WriteLine(ex.ToString()); |
|||
} |
|||
} |
|||
|
|||
private struct Stats |
|||
{ |
|||
public double TotalSeconds { get; } |
|||
|
|||
public double TotalMegapixels { get; } |
|||
|
|||
public double MegapixelsPerSec { get; } |
|||
|
|||
public double MegapixelsPerSecPerCpu { get; } |
|||
|
|||
public Stats(Stopwatch sw, double totalMegapixels) |
|||
{ |
|||
this.TotalMegapixels = totalMegapixels; |
|||
this.TotalSeconds = sw.ElapsedMilliseconds / 1000.0; |
|||
this.MegapixelsPerSec = totalMegapixels / this.TotalSeconds; |
|||
this.MegapixelsPerSecPerCpu = this.MegapixelsPerSec / Environment.ProcessorCount; |
|||
} |
|||
|
|||
public string GetMarkdown() |
|||
{ |
|||
var bld = new StringBuilder(); |
|||
bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |"); |
|||
bld.AppendLine( |
|||
$"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |"); |
|||
|
|||
bld.Append("| "); |
|||
bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); |
|||
bld.Append(" | "); |
|||
bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); |
|||
bld.Append(" | "); |
|||
bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); |
|||
bld.AppendLine(" |"); |
|||
|
|||
return bld.ToString(); |
|||
|
|||
static string L(string header) => new ('-', header.Length); |
|||
static string F(string column) => $"{{0,{column.Length}:f3}}"; |
|||
} |
|||
} |
|||
|
|||
private void ForEachImage(Action<string> action) => this.benchmarks.ForEachImageParallel(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 MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); |
|||
|
|||
private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); |
|||
|
|||
private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue