// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp.Benchmarks.Image { using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Numerics; using BenchmarkDotNet.Attributes; using Image = ImageSharp.Image; public abstract class MultiImageBenchmarkBase : BenchmarkBase { protected Dictionary FileNamesToBytes = new Dictionary(); protected Dictionary FileNamesToImageSharpImages = new Dictionary(); protected Dictionary FileNamesToSystemDrawingImages = new Dictionary(); /// /// The values of this enum separate input files into categories /// public enum InputImageCategory { AllImages, SmallImagesOnly, LargeImagesOnly } [Params(InputImageCategory.AllImages, InputImageCategory.SmallImagesOnly, InputImageCategory.LargeImagesOnly)] public virtual InputImageCategory InputCategory { get; set; } protected virtual string BaseFolder => "../ImageSharp.Tests/TestImages/Formats/"; protected virtual IEnumerable SearchPatterns => new[] { "*.*" }; /// /// Gets the file names containing these strings are substrings are not processed by the benchmark. /// protected IEnumerable ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof" }; /// /// Enumerates folders containing files OR files to be processed by the benchmark. /// protected IEnumerable AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); /// /// The images sized above this threshold will be included in /// protected virtual int LargeImageThresholdInBytes => 100000; protected IEnumerable> EnumeratePairsByBenchmarkSettings( Dictionary input, Predicate checkIfSmall) { switch (this.InputCategory) { case InputImageCategory.AllImages: return input; case InputImageCategory.SmallImagesOnly: return input.Where(kv => checkIfSmall(kv.Value)); case InputImageCategory.LargeImagesOnly: return input.Where(kv => !checkIfSmall(kv.Value)); default: throw new ArgumentOutOfRangeException(); } } protected IEnumerable> FileNames2Bytes => this.EnumeratePairsByBenchmarkSettings( this.FileNamesToBytes, arr => arr.Length < this.LargeImageThresholdInBytes); protected abstract IEnumerable InputImageSubfoldersOrFiles { get; } [Setup] public void ReadImages() { if (!Vector.IsHardwareAccelerated) { throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); } // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); this.ReadFilesImpl(); } protected virtual void ReadFilesImpl() { foreach (string path in this.AllFoldersOrFiles) { if (File.Exists(path)) { this.FileNamesToBytes[path] = File.ReadAllBytes(path); continue; } string[] allFiles = this.SearchPatterns.SelectMany( f => Directory.EnumerateFiles(path, f, SearchOption.AllDirectories) .Where(fn => !this.ExcludeSubstringsInFileNames.Any(w => fn.ToLower().Contains(w)))).ToArray(); foreach (string fn in allFiles) { this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); } } } /// /// Execute code for each image stream. If the returned object of the opearation is it will be disposed. /// /// The operation to execute. If the returned object is <see cref="IDisposable"/> it will be disposed protected void ForEachStream(Func operation) { foreach (KeyValuePair kv in this.FileNames2Bytes) { using (MemoryStream memoryStream = new MemoryStream(kv.Value)) { try { object obj = operation(memoryStream); (obj as IDisposable)?.Dispose(); } catch (Exception ex) { Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } } } } public abstract class WithImagesPreloaded : MultiImageBenchmarkBase { protected override void ReadFilesImpl() { base.ReadFilesImpl(); foreach (KeyValuePair kv in this.FileNamesToBytes) { byte[] bytes = kv.Value; string fn = kv.Key; using (MemoryStream ms1 = new MemoryStream(bytes)) { this.FileNamesToImageSharpImages[fn] = new Image(ms1); } this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); } } protected IEnumerable> FileNames2ImageSharpImages => this.EnumeratePairsByBenchmarkSettings( this.FileNamesToImageSharpImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected IEnumerable> FileNames2SystemDrawingImages => this.EnumeratePairsByBenchmarkSettings( this.FileNamesToSystemDrawingImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected virtual int LargeImageThresholdInPixels => 700000; protected void ForEachImageSharpImage(Func operation) { foreach (KeyValuePair kv in this.FileNames2ImageSharpImages) { try { object obj = operation(kv.Value); (obj as IDisposable)?.Dispose(); } catch (Exception ex) { Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } } } protected void ForEachImageSharpImage(Func operation) { using (MemoryStream workStream = new MemoryStream()) { this.ForEachImageSharpImage( img => { // ReSharper disable AccessToDisposedClosure object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin); // ReSharper restore AccessToDisposedClosure return result; }); } } protected void ForEachSystemDrawingImage(Func operation) { foreach (KeyValuePair kv in this.FileNames2SystemDrawingImages) { try { object obj = operation(kv.Value); (obj as IDisposable)?.Dispose(); } catch (Exception ex) { Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } } } protected void ForEachSystemDrawingImage(Func operation) { using (MemoryStream workStream = new MemoryStream()) { this.ForEachSystemDrawingImage( img => { // ReSharper disable AccessToDisposedClosure object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin); // ReSharper restore AccessToDisposedClosure return result; }); } } } } }