From 71801bfbb6120a0487cd9526e4fc9c0ce15c55c4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 17 Dec 2020 12:16:31 +0000 Subject: [PATCH 01/28] cleanup --- tests/ImageSharp.Benchmarks/BenchmarkBase.cs | 19 --- .../ImageSharp.Benchmarks/Codecs/DecodeBmp.cs | 29 ++--- .../Codecs/DecodeFilteredPng.cs | 48 +++----- .../ImageSharp.Benchmarks/Codecs/DecodeGif.cs | 29 ++--- .../ImageSharp.Benchmarks/Codecs/DecodePng.cs | 29 ++--- .../ImageSharp.Benchmarks/Codecs/DecodeTga.cs | 34 ++---- .../ImageSharp.Benchmarks/Codecs/EncodeBmp.cs | 23 ++-- .../Codecs/EncodeBmpMultiple.cs | 10 +- .../ImageSharp.Benchmarks/Codecs/EncodeGif.cs | 19 ++- .../Codecs/EncodeGifMultiple.cs | 10 +- .../Codecs/EncodeIndexedPng.cs | 59 ++++----- .../ImageSharp.Benchmarks/Codecs/EncodePng.cs | 19 ++- .../ImageSharp.Benchmarks/Codecs/EncodeTga.cs | 34 +++--- .../Codecs/GetSetPixel.cs | 32 ----- .../Codecs/Jpeg/CmykColorConversion.cs | 16 +-- .../Codecs/Jpeg/ColorConversionBenchmark.cs | 20 ++-- .../Codecs/Jpeg/DecodeJpegParseStreamOnly.cs | 7 +- .../Codecs/Jpeg/DecodeJpeg_Aggregate.cs | 16 +-- .../Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs | 20 +--- .../Codecs/Jpeg/EncodeJpeg.cs | 32 ++--- .../Codecs/Jpeg/EncodeJpegMultiple.cs | 10 +- .../Codecs/Jpeg/GrayscaleColorConversion.cs | 10 +- .../Codecs/Jpeg/IdentifyJpeg.cs | 13 +- .../Codecs/Jpeg/LoadResizeSave_Aggregate.cs | 64 +++++----- .../Jpeg/LoadResizeSave_ImageSpecific.cs | 32 +++-- .../Codecs/Jpeg/RgbColorConversion.cs | 14 +-- .../Codecs/Jpeg/YCbCrColorConversion.cs | 19 ++- .../Codecs/Jpeg/YccKColorConverter.cs | 14 +-- .../Codecs/MultiImageBenchmarkBase.cs | 112 +++++++----------- .../Color/Bulk/FromVector4_Rgb24.cs | 2 +- .../Color/Bulk/ToVector4_Bgra32.cs | 2 +- .../Color/Bulk/ToVector4_Rgb24.cs | 2 +- tests/ImageSharp.Benchmarks/Config.cs | 8 +- .../General/Adler32Benchmark.cs | 2 +- .../General/CopyBuffers.cs | 2 +- .../General/Crc32Benchmark.cs | 2 +- .../General/GetSetPixel.cs | 28 +++++ .../General/IO/BufferedStreams.cs | 2 +- .../General/Vectorization/UInt32ToSingle.cs | 2 +- .../Vectorization/WidenBytesToUInt32.cs | 2 +- .../ImageSharp.Benchmarks.csproj | 2 +- .../PixelBlenders/PorterDuffBulkVsPixel.cs | 86 ++++++-------- .../{Samplers => Processing}/BokehBlur.cs | 8 +- .../ImageSharp.Benchmarks/Processing/Crop.cs | 40 +++++++ .../{Samplers => Processing}/DetectEdges.cs | 20 ++-- .../{Samplers => Processing}/Diffuse.cs | 20 ++-- .../{Samplers => Processing}/GaussianBlur.cs | 6 +- .../Processing/HistogramEqualization.cs | 31 +++-- .../{Samplers => Processing}/Resize.cs | 36 ++---- .../{Samplers => Processing}/Rotate.cs | 12 +- .../{Samplers => Processing}/Skew.cs | 12 +- tests/ImageSharp.Benchmarks/Samplers/Crop.cs | 45 ------- 52 files changed, 472 insertions(+), 693 deletions(-) delete mode 100644 tests/ImageSharp.Benchmarks/BenchmarkBase.cs delete mode 100644 tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs create mode 100644 tests/ImageSharp.Benchmarks/General/GetSetPixel.cs rename tests/ImageSharp.Benchmarks/{Samplers => Processing}/BokehBlur.cs (60%) create mode 100644 tests/ImageSharp.Benchmarks/Processing/Crop.cs rename tests/ImageSharp.Benchmarks/{Samplers => Processing}/DetectEdges.cs (79%) rename tests/ImageSharp.Benchmarks/{Samplers => Processing}/Diffuse.cs (76%) rename tests/ImageSharp.Benchmarks/{Samplers => Processing}/GaussianBlur.cs (69%) rename tests/ImageSharp.Benchmarks/{Samplers => Processing}/Resize.cs (92%) rename tests/ImageSharp.Benchmarks/{Samplers => Processing}/Rotate.cs (81%) rename tests/ImageSharp.Benchmarks/{Samplers => Processing}/Skew.cs (81%) delete mode 100644 tests/ImageSharp.Benchmarks/Samplers/Crop.cs diff --git a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs deleted file mode 100644 index 5573b1382..000000000 --- a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Benchmarks -{ - /// - /// The image benchmark base class. - /// - public abstract class BenchmarkBase - { - /// - /// Initializes a new instance of the class. - /// - protected BenchmarkBase() - { - // Add Image Formats - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs index 58a97b65e..338f22156 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs @@ -10,12 +10,13 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodeBmp : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodeBmp { private byte[] bmpBytes; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); [GlobalSetup] public void ReadImages() @@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] public SDSize BmpSystemDrawing() { - using (var memoryStream = new MemoryStream(this.bmpBytes)) - { - using (var image = SDImage.FromStream(memoryStream)) - { - return image.Size; - } - } + using var memoryStream = new MemoryStream(this.bmpBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; } [Benchmark(Description = "ImageSharp Bmp")] - public Size BmpCore() + public Size BmpImageSharp() { - using (var memoryStream = new MemoryStream(this.bmpBytes)) - { - using (var image = Image.Load(memoryStream)) - { - return new Size(image.Width, image.Height); - } - } + using var memoryStream = new MemoryStream(this.bmpBytes); + using var image = Image.Load(memoryStream); + return new Size(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs index 3488d4405..6517bf3c4 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs @@ -6,12 +6,11 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using CoreSize = SixLabors.ImageSharp.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodeFilteredPng : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodeFilteredPng { private byte[] filter0; private byte[] filter1; @@ -30,44 +29,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Baseline = true, Description = "None-filtered PNG file")] - public CoreSize PngFilter0() - { - return LoadPng(this.filter0); - } + public Size PngFilter0() + => LoadPng(this.filter0); [Benchmark(Description = "Sub-filtered PNG file")] - public CoreSize PngFilter1() - { - return LoadPng(this.filter1); - } + public Size PngFilter1() + => LoadPng(this.filter1); [Benchmark(Description = "Up-filtered PNG file")] - public CoreSize PngFilter2() - { - return LoadPng(this.filter2); - } + public Size PngFilter2() + => LoadPng(this.filter2); [Benchmark(Description = "Average-filtered PNG file")] - public CoreSize PngFilter3() - { - return LoadPng(this.filter3); - } + public Size PngFilter3() + => LoadPng(this.filter3); [Benchmark(Description = "Paeth-filtered PNG file")] - public CoreSize PngFilter4() - { - return LoadPng(this.filter4); - } + public Size PngFilter4() + => LoadPng(this.filter4); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static CoreSize LoadPng(byte[] bytes) + private static Size LoadPng(byte[] bytes) { - using (var image = Image.Load(bytes)) - { - return image.Size(); - } + using var image = Image.Load(bytes); + return image.Size(); } - private static string TestImageFullPath(string path) => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + private static string TestImageFullPath(string path) + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs index f4cfddd88..3add96a68 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs @@ -10,12 +10,13 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodeGif : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodeGif { private byte[] gifBytes; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); [GlobalSetup] public void ReadImages() @@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Gif")] public SDSize GifSystemDrawing() { - using (var memoryStream = new MemoryStream(this.gifBytes)) - { - using (var image = SDImage.FromStream(memoryStream)) - { - return image.Size; - } - } + using var memoryStream = new MemoryStream(this.gifBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; } [Benchmark(Description = "ImageSharp Gif")] - public Size GifCore() + public Size GifImageSharp() { - using (var memoryStream = new MemoryStream(this.gifBytes)) - { - using (var image = Image.Load(memoryStream)) - { - return new Size(image.Width, image.Height); - } - } + using var memoryStream = new MemoryStream(this.gifBytes); + using var image = Image.Load(memoryStream); + return new Size(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs index 57ee308e7..66feb801f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs @@ -10,12 +10,13 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodePng : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodePng { private byte[] pngBytes; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); [Params(TestImages.Png.Splash)] public string TestImage { get; set; } @@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Png")] public SDSize PngSystemDrawing() { - using (var memoryStream = new MemoryStream(this.pngBytes)) - { - using (var image = SDImage.FromStream(memoryStream)) - { - return image.Size; - } - } + using var memoryStream = new MemoryStream(this.pngBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; } [Benchmark(Description = "ImageSharp Png")] - public Size PngCore() + public Size PngImageSharp() { - using (var memoryStream = new MemoryStream(this.pngBytes)) - { - using (var image = Image.Load(memoryStream)) - { - return image.Size(); - } - } + using var memoryStream = new MemoryStream(this.pngBytes); + using var image = Image.Load(memoryStream); + return image.Size(); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs index 4695d7ca4..c73fcc17b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs @@ -5,7 +5,6 @@ using System.Buffers; using System.IO; using System.Threading; using BenchmarkDotNet.Attributes; - using ImageMagick; using Pfim; using SixLabors.ImageSharp.Formats.Tga; @@ -14,8 +13,8 @@ using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodeTga : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodeTga { private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); @@ -28,36 +27,28 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [GlobalSetup] public void SetupData() - { - this.data = File.ReadAllBytes(this.TestImageFullPath); - } + => this.data = File.ReadAllBytes(this.TestImageFullPath); [Benchmark(Baseline = true, Description = "ImageMagick Tga")] public int TgaImageMagick() { var settings = new MagickReadSettings { Format = MagickFormat.Tga }; - using (var image = new MagickImage(new MemoryStream(this.data), settings)) - { - return image.Width; - } + using var image = new MagickImage(new MemoryStream(this.data), settings); + return image.Width; } [Benchmark(Description = "ImageSharp Tga")] - public int TgaCore() + public int TgaImageSharp() { - using (var image = Image.Load(this.data, new TgaDecoder())) - { - return image.Width; - } + using var image = Image.Load(this.data, new TgaDecoder()); + return image.Width; } [Benchmark(Description = "Pfim Tga")] public int TgaPfim() { - using (var image = Targa.Create(this.data, this.pfimConfig)) - { - return image.Width; - } + using var image = Targa.Create(this.data, this.pfimConfig); + return image.Width; } private class PfimAllocator : IImageAllocator @@ -65,10 +56,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private int rented; private readonly ArrayPool shared = ArrayPool.Shared; - public byte[] Rent(int size) - { - return this.shared.Rent(size); - } + public byte[] Rent(int size) => this.shared.Rent(size); public void Return(byte[] data) { diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs index c816aee2e..c9665457f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; @@ -10,8 +10,8 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class EncodeBmp : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodeBmp { private Stream bmpStream; private SDImage bmpDrawing; @@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); this.bmpDrawing.Dispose(); } @@ -40,19 +41,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] public void BmpSystemDrawing() { - using (var memoryStream = new MemoryStream()) - { - this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp); - } + using var memoryStream = new MemoryStream(); + this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp); } [Benchmark(Description = "ImageSharp Bmp")] - public void BmpCore() + public void BmpImageSharp() { - using (var memoryStream = new MemoryStream()) - { - this.bmpCore.SaveAsBmp(memoryStream); - } + using var memoryStream = new MemoryStream(); + this.bmpCore.SaveAsBmp(memoryStream); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs index a7ffbe46e..60dbdd666 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs @@ -8,29 +8,25 @@ using SixLabors.ImageSharp.Formats.Bmp; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class EncodeBmpMultiple : MultiImageBenchmarkBase.WithImagesPreloaded { protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] public void EncodeBmpImageSharp() - { - this.ForEachImageSharpImage((img, ms) => + => this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new BmpEncoder()); return null; }); - } [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] public void EncodeBmpSystemDrawing() - { - this.ForEachSystemDrawingImage((img, ms) => + => this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Bmp); return null; }); - } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs index b6ce67bfd..26d5c4bc1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -13,8 +13,8 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class EncodeGif : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodeGif { // System.Drawing needs this. private Stream bmpStream; @@ -46,6 +46,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); this.bmpDrawing.Dispose(); } @@ -53,19 +54,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Gif")] public void GifSystemDrawing() { - using (var memoryStream = new MemoryStream()) - { - this.bmpDrawing.Save(memoryStream, ImageFormat.Gif); - } + using var memoryStream = new MemoryStream(); + this.bmpDrawing.Save(memoryStream, ImageFormat.Gif); } [Benchmark(Description = "ImageSharp Gif")] - public void GifCore() + public void GifImageSharp() { - using (var memoryStream = new MemoryStream()) - { - this.bmpCore.SaveAsGif(memoryStream, this.encoder); - } + using var memoryStream = new MemoryStream(); + this.bmpCore.SaveAsGif(memoryStream, this.encoder); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs index 179e6946a..6fea9315a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded { [Params(InputImageCategory.AllImages)] @@ -20,8 +20,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "EncodeGifMultiple - ImageSharp")] public void EncodeGifImageSharp() - { - this.ForEachImageSharpImage((img, ms) => + => this.ForEachImageSharpImage((img, ms) => { // Try to get as close to System.Drawing's output as possible var options = new GifEncoder @@ -32,16 +31,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs img.Save(ms, options); return null; }); - } [Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")] public void EncodeGifSystemDrawing() - { - this.ForEachSystemDrawingImage((img, ms) => + => this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Gif); return null; }); - } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs index b3113e6d7..de8d41202 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs @@ -8,15 +8,15 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests; -using CoreImage = SixLabors.ImageSharp.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { /// - /// Benchmarks saving png files using different quantizers. System.Drawing cannot save indexed png files so we cannot compare. + /// Benchmarks saving png files using different quantizers. + /// System.Drawing cannot save indexed png files so we cannot compare. /// - [Config(typeof(Config.ShortClr))] - public class EncodeIndexedPng : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodeIndexedPng { // System.Drawing needs this. private Stream bmpStream; @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs if (this.bmpStream == null) { this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); - this.bmpCore = CoreImage.Load(this.bmpStream); + this.bmpCore = Image.Load(this.bmpStream); this.bmpStream.Position = 0; } } @@ -37,67 +37,56 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); } [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] public void PngCoreOctree() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = KnownQuantizers.Octree }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = KnownQuantizers.Octree }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Octree NoDither Png")] public void PngCoreOctreeNoDither() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Palette Png")] public void PngCorePalette() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Palette NoDither Png")] public void PngCorePaletteNoDither() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Wu Png")] public void PngCoreWu() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = KnownQuantizers.Wu }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = KnownQuantizers.Wu }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Wu NoDither Png")] public void PngCoreWuNoDither() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; + this.bmpCore.SaveAsPng(memoryStream, options); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs index 81b884b75..c628b7109 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs @@ -11,8 +11,8 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class EncodePng : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodePng { // System.Drawing needs this. private Stream bmpStream; @@ -39,6 +39,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); this.bmpDrawing.Dispose(); } @@ -46,20 +47,16 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Png")] public void PngSystemDrawing() { - using (var memoryStream = new MemoryStream()) - { - this.bmpDrawing.Save(memoryStream, ImageFormat.Png); - } + using var memoryStream = new MemoryStream(); + this.bmpDrawing.Save(memoryStream, ImageFormat.Png); } [Benchmark(Description = "ImageSharp Png")] public void PngCore() { - using (var memoryStream = new MemoryStream()) - { - var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; - this.bmpCore.SaveAsPng(memoryStream, encoder); - } + using var memoryStream = new MemoryStream(); + var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; + this.bmpCore.SaveAsPng(memoryStream, encoder); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs index 37cfa314c..bcb015e57 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs @@ -2,23 +2,21 @@ // Licensed under the Apache License, Version 2.0. using System.IO; - using BenchmarkDotNet.Attributes; - using ImageMagick; - using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class EncodeTga : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodeTga { private MagickImage tgaMagick; private Image tgaCore; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); [Params(TestImages.Tga.Bit24BottomLeft)] public string TestImage { get; set; } @@ -33,22 +31,26 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } + [GlobalCleanup] + public void Cleanup() + { + this.tgaCore.Dispose(); + this.tgaCore = null; + this.tgaMagick.Dispose(); + } + [Benchmark(Baseline = true, Description = "Magick Tga")] - public void BmpSystemDrawing() + public void BmpImageMagick() { - using (var memoryStream = new MemoryStream()) - { - this.tgaMagick.Write(memoryStream, MagickFormat.Tga); - } + using var memoryStream = new MemoryStream(); + this.tgaMagick.Write(memoryStream, MagickFormat.Tga); } [Benchmark(Description = "ImageSharp Tga")] - public void BmpCore() + public void BmpImageSharp() { - using (var memoryStream = new MemoryStream()) - { - this.tgaCore.SaveAsBmp(memoryStream); - } + using var memoryStream = new MemoryStream(); + this.tgaCore.SaveAsBmp(memoryStream); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs deleted file mode 100644 index 197f0804d..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs -{ - public class GetSetPixel : BenchmarkBase - { - [Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")] - public System.Drawing.Color ResizeSystemDrawing() - { - using (var source = new Bitmap(400, 400)) - { - source.SetPixel(200, 200, System.Drawing.Color.White); - return source.GetPixel(200, 200); - } - } - - [Benchmark(Description = "ImageSharp GetSet pixel")] - public Rgba32 ResizeCore() - { - using (var image = new Image(400, 400)) - { - image[200, 200] = Color.White; - return image[200, 200]; - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs index 1e6b9fe92..d17882adf 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class CmykColorConversion : ColorConversionBenchmark { public CmykColorConversion() @@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromCmykBasic(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykVector8(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromCmykVector8(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromCmykAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs index da8c51735..e93ad474b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -11,27 +11,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public abstract class ColorConversionBenchmark { private readonly int componentCount; - protected Buffer2D[] input; - protected Vector4[] output; + + public const int Count = 128; protected ColorConversionBenchmark(int componentCount) - { - this.componentCount = componentCount; - } + => this.componentCount = componentCount; - public const int Count = 128; + protected Buffer2D[] Input { get; private set; } + + protected Vector4[] Output { get; private set; } [GlobalSetup] public void Setup() { - this.input = CreateRandomValues(this.componentCount, Count); - this.output = new Vector4[Count]; + this.Input = CreateRandomValues(this.componentCount, Count); + this.Output = new Vector4[Count]; } [GlobalCleanup] public void Cleanup() { - foreach (Buffer2D buffer in this.input) + foreach (Buffer2D buffer in this.Input) { buffer.Dispose(); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 0e68af87a..68a102e3c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -3,7 +3,6 @@ using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Tests; @@ -11,7 +10,7 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class DecodeJpegParseStreamOnly { [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] @@ -23,9 +22,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [GlobalSetup] public void Setup() - { - this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); - } + => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); [Benchmark(Baseline = true, Description = "System.Drawing FULL")] public SDSize JpegSystemDrawing() diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs index b92a66ebd..cabc0ed91 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs @@ -2,22 +2,20 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; - using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; - using SDImage = System.Drawing.Image; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { /// - /// An expensive Jpeg benchmark, running on a wide range of input images, showing aggregate results. + /// An expensive Jpeg benchmark, running on a wide range of input images, + /// showing aggregate results. /// - [Config(typeof(MultiImageBenchmarkBase.Config))] + [Config(typeof(Config.ShortMultiFramework))] public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase { protected override IEnumerable InputImageSubfoldersOrFiles => @@ -35,14 +33,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark] public void ImageSharp() - { - this.ForEachStream(ms => Image.Load(ms, new JpegDecoder())); - } + => this.ForEachStream(ms => Image.Load(ms, new JpegDecoder())); [Benchmark(Baseline = true)] public void SystemDrawing() - { - this.ForEachStream(SDImage.FromStream); - } + => this.ForEachStream(SDImage.FromStream); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index 65866e8ba..b0ac1c0fc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -3,11 +3,6 @@ using System.IO; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Environments; -using BenchmarkDotNet.Jobs; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; @@ -20,22 +15,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg /// /// Image-specific Jpeg benchmarks /// - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class DecodeJpeg_ImageSpecific { - public class Config : ManualConfig - { - public Config() => this.AddDiagnoser(MemoryDiagnoser.Default); - - public class ShortClr : Benchmarks.Config - { - public ShortClr() => - - // Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3)); - } - } - private byte[] jpegBytes; private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index 4c326fb3a..bfbd150fe 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -1,23 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Drawing.Imaging; +using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - using System.Drawing; - using System.Drawing.Imaging; - using System.IO; - using SixLabors.ImageSharp.Tests; - using CoreImage = SixLabors.ImageSharp.Image; - - public class EncodeJpeg : BenchmarkBase + public class EncodeJpeg { // System.Drawing needs this. private Stream bmpStream; - private Image bmpDrawing; + private SDImage bmpDrawing; private Image bmpCore; [GlobalSetup] @@ -27,9 +24,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { const string TestImage = TestImages.Bmp.Car; this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); - this.bmpCore = CoreImage.Load(this.bmpStream); + this.bmpCore = Image.Load(this.bmpStream); this.bmpStream.Position = 0; - this.bmpDrawing = Image.FromStream(this.bmpStream); + this.bmpDrawing = SDImage.FromStream(this.bmpStream); } } @@ -37,6 +34,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); this.bmpDrawing.Dispose(); } @@ -44,19 +42,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] public void JpegSystemDrawing() { - using (var stream = new MemoryStream()) - { - this.bmpDrawing.Save(stream, ImageFormat.Jpeg); - } + using var stream = new MemoryStream(); + this.bmpDrawing.Save(stream, ImageFormat.Jpeg); } [Benchmark(Description = "ImageSharp Jpeg")] public void JpegCore() { - using (var stream = new MemoryStream()) - { - this.bmpCore.SaveAsJpeg(stream); - } + using var stream = new MemoryStream(); + this.bmpCore.SaveAsJpeg(stream); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs index 14b240339..71276597a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Formats.Jpeg; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] // It's long enough to iterate through multiple files + [Config(typeof(Config.ShortMultiFramework))] public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded { protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; @@ -17,22 +17,18 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Description = "EncodeJpegMultiple - ImageSharp")] public void EncodeJpegImageSharp() - { - this.ForEachImageSharpImage((img, ms) => + => this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new JpegEncoder()); return null; }); - } [Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")] public void EncodeJpegSystemDrawing() - { - this.ForEachSystemDrawingImage((img, ms) => + => this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Jpeg); return null; }); - } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs index 74b3e6db6..1eba1571d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class GrayscaleColorConversion : ColorConversionBenchmark { public GrayscaleColorConversion() @@ -17,17 +17,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromGrayscaleBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromGrayscaleBasic(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromGrayscaleAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromGrayscaleAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs index 43d4ccf07..aae144ce0 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs @@ -1,15 +1,14 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class IdentifyJpeg { private byte[] jpegBytes; @@ -31,11 +30,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark] public IImageInfo Identify() { - using (var memoryStream = new MemoryStream(this.jpegBytes)) - { - var decoder = new JpegDecoder(); - return decoder.Identify(Configuration.Default, memoryStream); - } + using var memoryStream = new MemoryStream(this.jpegBytes); + var decoder = new JpegDecoder(); + return decoder.Identify(Configuration.Default, memoryStream); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs index 2edc3e7af..731850c43 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs @@ -6,9 +6,7 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; - using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -17,7 +15,7 @@ using SixLabors.ImageSharp.Tests; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(MultiImageBenchmarkBase.Config))] + [Config(typeof(Config.ShortMultiFramework))] public class LoadResizeSave_Aggregate : MultiImageBenchmarkBase { protected override IEnumerable InputImageSubfoldersOrFiles => @@ -48,49 +46,43 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void SystemDrawing() - { - this.ForEachStream( + => this.ForEachStream( sourceStream => + { + using (var destStream = new MemoryStream(this.destBytes)) + using (var source = System.Drawing.Image.FromStream(sourceStream)) + using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) { - using (var destStream = new MemoryStream(this.destBytes)) - using (var source = System.Drawing.Image.FromStream(sourceStream)) - using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) + using (var g = Graphics.FromImage(destination)) { - using (var g = Graphics.FromImage(destination)) - { - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - g.DrawImage(source, 0, 0, 400, 400); - } - - destination.Save(destStream, ImageFormat.Jpeg); + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + g.DrawImage(source, 0, 0, 400, 400); } - return null; - }); - } + destination.Save(destStream, ImageFormat.Jpeg); + } + + return null; + }); [Benchmark] public void ImageSharp() - { - this.ForEachStream( + => this.ForEachStream( sourceStream => + { + using (var source = Image.Load( + this.configuration, + sourceStream, + new JpegDecoder { IgnoreMetadata = true })) { - using (var source = Image.Load( - this.configuration, - sourceStream, - new JpegDecoder { IgnoreMetadata = true })) - { - using (var destStream = new MemoryStream(this.destBytes)) - { - source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); - source.SaveAsJpeg(destStream); - } - } + using var destStream = new MemoryStream(this.destBytes); + source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); + source.SaveAsJpeg(destStream); + } - return null; - }); - } + return null; + }); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs index b46e74777..cfa39bd5a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs @@ -7,17 +7,15 @@ using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; - using SDImage = System.Drawing.Image; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class LoadResizeSave_ImageSpecific { private readonly Configuration configuration = new Configuration(new JpegConfigurationModule()); @@ -32,6 +30,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] + public string TestImage { get; set; } [Params(false, true)] @@ -51,28 +50,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void SystemDrawing() { - using (var sourceStream = new MemoryStream(this.sourceBytes)) - using (var destStream = new MemoryStream(this.destBytes)) - using (var source = SDImage.FromStream(sourceStream)) - using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) + using var sourceStream = new MemoryStream(this.sourceBytes); + using var destStream = new MemoryStream(this.destBytes); + using var source = SDImage.FromStream(sourceStream); + using var destination = new Bitmap(source.Width / 4, source.Height / 4); + using (var g = Graphics.FromImage(destination)) { - using (var g = Graphics.FromImage(destination)) - { - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - g.DrawImage(source, 0, 0, 400, 400); - } - - destination.Save(destStream, ImageFormat.Jpeg); + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + g.DrawImage(source, 0, 0, 400, 400); } + + destination.Save(destStream, ImageFormat.Jpeg); } [Benchmark] public void ImageSharp() { - var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true }); - using (source) + using (var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true })) using (var destStream = new MemoryStream(this.destBytes)) { source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs index 68dc0f04e..c1598be04 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class RgbColorConversion : ColorConversionBenchmark { public RgbColorConversion() @@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromRgbBasic(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbVector8(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromRgbVector8(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromRgbAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs index 94b28e4d9..6c5732c99 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs @@ -2,12 +2,11 @@ // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class YCbCrColorConversion : ColorConversionBenchmark { public YCbCrColorConversion() @@ -18,33 +17,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYCbCrBasic(8).ConvertToRgba(values, this.Output); } [Benchmark(Baseline = true)] public void SimdVector() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrVector4(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYCbCrVector4(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrVector8(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYCbCrVector8(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYCbCrAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs index ed8758131..0a9bdb8fd 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class YccKColorConverter : ColorConversionBenchmark { public YccKColorConverter() @@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYccKBasic(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKVector8(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYccKVector8(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYccKAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs index c2f6eab3c..b7e1e8ddb 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs @@ -8,33 +8,18 @@ using System.IO; using System.Linq; using System.Numerics; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Environments; -using BenchmarkDotNet.Jobs; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using CoreImage = SixLabors.ImageSharp.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { public abstract class MultiImageBenchmarkBase { - public class Config : ManualConfig - { - public Config() => this.AddDiagnoser(MemoryDiagnoser.Default); - - public class ShortClr : Benchmarks.Config - { - public ShortClr() => this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2)); - } - } - protected Dictionary FileNamesToBytes { get; set; } = new Dictionary(); protected Dictionary> FileNamesToImageSharpImages { get; set; } = new Dictionary>(); - protected Dictionary FileNamesToSystemDrawingImages { get; set; } = new Dictionary(); + protected Dictionary FileNamesToSystemDrawingImages { get; set; } = new Dictionary(); /// /// The values of this enum separate input files into categories. @@ -72,7 +57,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs /// /// Gets folders containing files OR files to be processed by the benchmark. /// - protected IEnumerable AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); + protected IEnumerable AllFoldersOrFiles + => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); /// /// Gets the large image threshold. @@ -83,19 +69,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected IEnumerable> EnumeratePairsByBenchmarkSettings( Dictionary input, Predicate checkIfSmall) - { - switch (this.InputCategory) + => this.InputCategory switch { - 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(); - } - } + InputImageCategory.AllImages => input, + InputImageCategory.SmallImagesOnly => input.Where(kv => checkIfSmall(kv.Value)), + InputImageCategory.LargeImagesOnly => input.Where(kv => !checkIfSmall(kv.Value)), + _ => throw new ArgumentOutOfRangeException(), + }; protected IEnumerable> FileNames2Bytes => @@ -150,17 +130,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { foreach (KeyValuePair kv in this.FileNames2Bytes) { - using (var memoryStream = new MemoryStream(kv.Value)) + using var memoryStream = new MemoryStream(kv.Value); + try { - try - { - object obj = operation(memoryStream); - (obj as IDisposable)?.Dispose(); - } - catch (Exception ex) - { - Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); - } + object obj = operation(memoryStream); + (obj as IDisposable)?.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } } } @@ -178,7 +156,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs using (var ms1 = new MemoryStream(bytes)) { - this.FileNamesToImageSharpImages[fn] = CoreImage.Load(ms1); + this.FileNamesToImageSharpImages[fn] = Image.Load(ms1); } this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); @@ -191,7 +169,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs this.FileNamesToImageSharpImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); - protected IEnumerable> FileNames2SystemDrawingImages + protected IEnumerable> FileNames2SystemDrawingImages => this.EnumeratePairsByBenchmarkSettings( this.FileNamesToSystemDrawingImages, @@ -217,22 +195,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected void ForEachImageSharpImage(Func, MemoryStream, object> operation) { - using (var workStream = new MemoryStream()) - { - this.ForEachImageSharpImage( - img => - { - // ReSharper disable AccessToDisposedClosure - object result = operation(img, workStream); - workStream.Seek(0, SeekOrigin.Begin); - - // ReSharper restore AccessToDisposedClosure - return result; - }); - } + using var 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) + protected void ForEachSystemDrawingImage(Func operation) { foreach (KeyValuePair kv in this.FileNames2SystemDrawingImages) { @@ -248,21 +224,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } - protected void ForEachSystemDrawingImage(Func operation) + protected void ForEachSystemDrawingImage(Func operation) { - using (var workStream = new MemoryStream()) - { - this.ForEachSystemDrawingImage( - img => - { - // ReSharper disable AccessToDisposedClosure - object result = operation(img, workStream); - workStream.Seek(0, SeekOrigin.Begin); - - // ReSharper restore AccessToDisposedClosure - return result; - }); - } + using var workStream = new MemoryStream(); + this.ForEachSystemDrawingImage( + img => + { + // ReSharper disable AccessToDisposedClosure + object result = operation(img, workStream); + workStream.Seek(0, SeekOrigin.Begin); + + // ReSharper restore AccessToDisposedClosure + return result; + }); } } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs index 5da6edc6b..ff55cc5d0 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class FromVector4_Rgb24 : FromVector4 { } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs index b090c7dc2..0426dcf09 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class ToVector4_Bgra32 : ToVector4 { [Benchmark(Baseline = true)] diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs index aecd41831..63a3148aa 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class ToVector4_Rgb24 : ToVector4 { [Benchmark(Baseline = true)] diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index d08e2f2d6..0c40b482a 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -35,12 +35,12 @@ namespace SixLabors.ImageSharp.Benchmarks Job.Default.WithRuntime(CoreRuntime.Core31)); } - public class ShortClr : Config + public class ShortMultiFramework : Config { - public ShortClr() => this.AddJob( + public ShortMultiFramework() => this.AddJob( Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), - Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), - Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); } public class ShortCore31 : Config diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs index eba4bcbb4..2d0fce0a0 100644 --- a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs +++ b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs @@ -8,7 +8,7 @@ using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; namespace SixLabors.ImageSharp.Benchmarks.General { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class Adler32Benchmark { private byte[] data; diff --git a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs index 96a95942c..d21e44a1c 100644 --- a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs +++ b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General /// - Span.CopyTo() has terrible performance on classic .NET Framework /// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning) /// - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class CopyBuffers { private byte[] destArray; diff --git a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs index 2dcf03627..0153ca8b1 100644 --- a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs +++ b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs @@ -8,7 +8,7 @@ using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32; namespace SixLabors.ImageSharp.Benchmarks.General { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class Crc32Benchmark { private byte[] data; diff --git a/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs new file mode 100644 index 000000000..6bb269e4c --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public class GetSetPixel + { + [Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")] + public System.Drawing.Color GetSetSystemDrawing() + { + using var source = new Bitmap(400, 400); + source.SetPixel(200, 200, System.Drawing.Color.White); + return source.GetPixel(200, 200); + } + + [Benchmark(Description = "ImageSharp GetSet pixel")] + public Rgba32 GetSetImageSharp() + { + using var image = new Image(400, 400); + image[200, 200] = Color.White; + return image[200, 200]; + } + } +} diff --git a/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs index f2ff49d4e..f5cf5211e 100644 --- a/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs +++ b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.IO; namespace SixLabors.ImageSharp.Benchmarks.IO { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class BufferedStreams { private readonly byte[] buffer = CreateTestBytes(); diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs index 15c4b8c05..5ec5f1d18 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs @@ -8,7 +8,7 @@ using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class UInt32ToSingle { private float[] data; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs index a0049f984..20d3bd9d6 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class WidenBytesToUInt32 { private byte[] source; diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index e8ad660ad..2a209683b 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -43,7 +43,7 @@ - + diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index 2b6657a22..cf7807837 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -1,21 +1,17 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Numerics; - using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.Benchmarks { - using CoreSize = SixLabors.ImageSharp.Size; - - public class PorterDuffBulkVsPixel : BenchmarkBase + public class PorterDuffBulkVsPixel { private Configuration Configuration => Configuration.Default; @@ -30,23 +26,21 @@ namespace SixLabors.ImageSharp.Benchmarks Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IMemoryOwner buffer = - Configuration.Default.MemoryAllocator.Allocate(destination.Length * 3)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + using IMemoryOwner buffer = + Configuration.Default.MemoryAllocator.Allocate(destination.Length * 3); + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - PixelOperations.Instance.ToVector4(this.Configuration, background, backgroundSpan); - PixelOperations.Instance.ToVector4(this.Configuration, source, sourceSpan); + PixelOperations.Instance.ToVector4(this.Configuration, background, backgroundSpan); + PixelOperations.Instance.ToVector4(this.Configuration, source, sourceSpan); - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } - - PixelOperations.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination); + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); } + + PixelOperations.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination); } private void BulkPixelConvert( @@ -67,44 +61,36 @@ namespace SixLabors.ImageSharp.Benchmarks } [Benchmark(Description = "ImageSharp BulkVectorConvert")] - public CoreSize BulkVectorConvert() + public Size BulkVectorConvert() { - using (var image = new Image(800, 800)) - { - using (IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width)) - { - amounts.GetSpan().Fill(1); + using var image = new Image(800, 800); + using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); + amounts.GetSpan().Fill(1); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int y = 0; y < image.Height; y++) - { - Span span = pixels.GetRowSpan(y); - this.BulkVectorConvert(span, span, span, amounts.GetSpan()); - } - - return new CoreSize(image.Width, image.Height); - } + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.GetRowSpan(y); + this.BulkVectorConvert(span, span, span, amounts.GetSpan()); } + + return new Size(image.Width, image.Height); } [Benchmark(Description = "ImageSharp BulkPixelConvert")] - public CoreSize BulkPixelConvert() + public Size BulkPixelConvert() { - using (var image = new Image(800, 800)) + using var image = new Image(800, 800); + using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); + amounts.GetSpan().Fill(1); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int y = 0; y < image.Height; y++) { - using (IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width)) - { - amounts.GetSpan().Fill(1); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int y = 0; y < image.Height; y++) - { - Span span = pixels.GetRowSpan(y); - this.BulkPixelConvert(span, span, span, amounts.GetSpan()); - } - - return new CoreSize(image.Width, image.Height); - } + Span span = pixels.GetRowSpan(y); + this.BulkPixelConvert(span, span, span, amounts.GetSpan()); } + + return new Size(image.Width, image.Height); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/BokehBlur.cs b/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs similarity index 60% rename from tests/ImageSharp.Benchmarks/Samplers/BokehBlur.cs rename to tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs index 1c3b1a7b2..7a1c7260e 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/BokehBlur.cs +++ b/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs @@ -5,7 +5,7 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Samplers +namespace SixLabors.ImageSharp.Benchmarks.Processing { [Config(typeof(Config.MultiFramework))] public class BokehBlur @@ -13,10 +13,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public void Blur() { - using (var image = new Image(Configuration.Default, 400, 400, Color.White)) - { - image.Mutate(c => c.BokehBlur()); - } + using var image = new Image(Configuration.Default, 400, 400, Color.White); + image.Mutate(c => c.BokehBlur()); } } } diff --git a/tests/ImageSharp.Benchmarks/Processing/Crop.cs b/tests/ImageSharp.Benchmarks/Processing/Crop.cs new file mode 100644 index 000000000..9f08adc9d --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Processing/Crop.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SDRectangle = System.Drawing.Rectangle; +using SDSize = System.Drawing.Size; + +namespace SixLabors.ImageSharp.Benchmarks.Processing +{ + [Config(typeof(Config.MultiFramework))] + public class Crop + { + [Benchmark(Baseline = true, Description = "System.Drawing Crop")] + public SDSize CropSystemDrawing() + { + using var source = new Bitmap(800, 800); + using var destination = new Bitmap(100, 100); + using var graphics = Graphics.FromImage(destination); + + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); + + return destination.Size; + } + + [Benchmark(Description = "ImageSharp Crop")] + public Size CropImageSharp() + { + using var image = new Image(800, 800); + image.Mutate(x => x.Crop(100, 100)); + return new Size(image.Width, image.Height); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs similarity index 79% rename from tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs rename to tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs index ce2fa988c..f3d6d03af 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs @@ -1,18 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.IO; +using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks { - using System.IO; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp.Processing; - using CoreImage = SixLabors.ImageSharp.Image; - - public class DetectEdges : BenchmarkBase + [Config(typeof(Config.MultiFramework))] + public class DetectEdges { private Image image; @@ -21,10 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks { if (this.image == null) { - using (FileStream stream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp")) - { - this.image = CoreImage.Load(stream); - } + this.image = Image.Load(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car))); } } @@ -32,6 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks public void Cleanup() { this.image.Dispose(); + this.image = null; } [Benchmark(Description = "ImageSharp DetectEdges")] diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs similarity index 76% rename from tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs rename to tests/ImageSharp.Benchmarks/Processing/Diffuse.cs index 354d105e6..d706938c8 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs @@ -5,31 +5,27 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Samplers +namespace SixLabors.ImageSharp.Benchmarks.Processing { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.MultiFramework))] public class Diffuse { [Benchmark] public Size DoDiffuse() { - using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) - { - image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); + using var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond); + image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); - return image.Size(); - } + return image.Size(); } [Benchmark] public Size DoDither() { - using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) - { - image.Mutate(x => x.Dither()); + using var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond); + image.Mutate(x => x.Dither()); - return image.Size(); - } + return image.Size(); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs similarity index 69% rename from tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs rename to tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs index 8f009e58f..8a4ab8747 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs +++ b/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs @@ -13,10 +13,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public void Blur() { - using (var image = new Image(Configuration.Default, 400, 400, Color.White)) - { - image.Mutate(c => c.GaussianBlur()); - } + using var image = new Image(Configuration.Default, 400, 400, Color.White); + image.Mutate(c => c.GaussianBlur()); } } } diff --git a/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs b/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs index 081d3e8e3..cfcb69a0a 100644 --- a/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs +++ b/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs @@ -10,8 +10,8 @@ using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Processing { - [Config(typeof(Config.ShortClr))] - public class HistogramEqualization : BenchmarkBase + [Config(typeof(Config.MultiFramework))] + public class HistogramEqualization { private Image image; @@ -28,26 +28,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Processing public void Cleanup() { this.image.Dispose(); + this.image = null; } [Benchmark(Description = "Global Histogram Equalization")] public void GlobalHistogramEqualization() - { - this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions() - { - LuminanceLevels = 256, - Method = HistogramEqualizationMethod.Global - })); - } + => this.image.Mutate(img => img.HistogramEqualization( + new HistogramEqualizationOptions() + { + LuminanceLevels = 256, + Method = HistogramEqualizationMethod.Global + })); [Benchmark(Description = "AdaptiveHistogramEqualization (Tile interpolation)")] public void AdaptiveHistogramEqualization() - { - this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions() - { - LuminanceLevels = 256, - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation - })); - } + => this.image.Mutate(img => img.HistogramEqualization( + new HistogramEqualizationOptions() + { + LuminanceLevels = 256, + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation + })); } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Processing/Resize.cs similarity index 92% rename from tests/ImageSharp.Benchmarks/Samplers/Resize.cs rename to tests/ImageSharp.Benchmarks/Processing/Resize.cs index 63a85c757..3823f7ec6 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Resize.cs @@ -4,27 +4,23 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Globalization; - using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Benchmarks { - [Config(typeof(Config.ShortClr))] -#pragma warning disable SA1649 // File name should match first type name - public abstract class ResizeBenchmarkBase -#pragma warning restore SA1649 // File name should match first type name + [Config(typeof(Config.MultiFramework))] + public abstract class Resize where TPixel : unmanaged, IPixel { - protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule()); - private Image sourceImage; private Bitmap sourceBitmap; + protected Configuration Configuration { get; } = new Configuration(new JpegConfigurationModule()); + [Params("3032-400")] public virtual string SourceToDest { get; set; } @@ -96,12 +92,10 @@ namespace SixLabors.ImageSharp.Benchmarks protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); } - public class Resize_Bicubic_Rgba32 : ResizeBenchmarkBase + public class Resize_Bicubic_Rgba32 : Resize { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - { - ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - } + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); // RESULTS - 2019 April - ResizeWorker: // @@ -143,12 +137,10 @@ namespace SixLabors.ImageSharp.Benchmarks } } - public class Resize_Bicubic_Bgra32 : ResizeBenchmarkBase + public class Resize_Bicubic_Bgra32 : Resize { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - { - ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - } + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); // RESULTS (2019 April): // @@ -171,12 +163,10 @@ namespace SixLabors.ImageSharp.Benchmarks // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 96.96 ms | 7.899 ms | 0.4329 ms | 0.80 | - | - | - | 44512 B | } - public class Resize_Bicubic_Rgb24 : ResizeBenchmarkBase + public class Resize_Bicubic_Rgb24 : Resize { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - { - ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - } + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); // RESULTS (2019 April): // @@ -197,12 +187,10 @@ namespace SixLabors.ImageSharp.Benchmarks // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 92.47 ms | 5.683 ms | 0.3115 ms | 0.78 | 0.01 | - | - | - | 44512 B | } - public class Resize_BicubicCompand_Rgba32 : ResizeBenchmarkBase + public class Resize_BicubicCompand_Rgba32 : Resize { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - { - ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); - } + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); // RESULTS (2019 April): // diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Processing/Rotate.cs similarity index 81% rename from tests/ImageSharp.Benchmarks/Samplers/Rotate.cs rename to tests/ImageSharp.Benchmarks/Processing/Rotate.cs index 94594c787..107c47f06 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Rotate.cs @@ -5,20 +5,18 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Samplers +namespace SixLabors.ImageSharp.Benchmarks.Processing { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.MultiFramework))] public class Rotate { [Benchmark] public Size DoRotate() { - using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) - { - image.Mutate(x => x.Rotate(37.5F)); + using var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond); + image.Mutate(x => x.Rotate(37.5F)); - return image.Size(); - } + return image.Size(); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Processing/Skew.cs similarity index 81% rename from tests/ImageSharp.Benchmarks/Samplers/Skew.cs rename to tests/ImageSharp.Benchmarks/Processing/Skew.cs index 2758bed7a..b77f0dcd6 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Skew.cs @@ -5,20 +5,18 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Samplers +namespace SixLabors.ImageSharp.Benchmarks.Processing { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.MultiFramework))] public class Skew { [Benchmark] public Size DoSkew() { - using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) - { - image.Mutate(x => x.Skew(20, 10)); + using var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond); + image.Mutate(x => x.Skew(20, 10)); - return image.Size(); - } + return image.Size(); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs deleted file mode 100644 index a62b68557..000000000 --- a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -using SDRectangle = System.Drawing.Rectangle; -using SDSize = System.Drawing.Size; - -namespace SixLabors.ImageSharp.Benchmarks -{ - [Config(typeof(Config.ShortClr))] - public class Crop : BenchmarkBase - { - [Benchmark(Baseline = true, Description = "System.Drawing Crop")] - public SDSize CropSystemDrawing() - { - using (var source = new Bitmap(800, 800)) - using (var destination = new Bitmap(100, 100)) - using (var graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); - - return destination.Size; - } - } - - [Benchmark(Description = "ImageSharp Crop")] - public Size CropResizeCore() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.Crop(100, 100)); - return new Size(image.Width, image.Height); - } - } - } -} From 53be61e5c502a483fac7a45d0f56391d566bdf83 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 17 Dec 2020 12:22:36 +0000 Subject: [PATCH 02/28] Update shared-infrastructure --- shared-infrastructure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-infrastructure b/shared-infrastructure index 3ad6e96a5..b7b9a2755 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 3ad6e96a5f900fecd134f0dbba937cb97c7fb94f +Subproject commit b7b9a2755e456a96acbf103494228226d92eddf3 From 89007db1a61f4f0773cb8b013aeee39f746396a7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 17 Dec 2020 13:09:11 +0000 Subject: [PATCH 03/28] Minor style cleanup. --- .../Processors/Transforms/Resize/ResizeKernel.cs | 8 ++------ .../Processors/Transforms/Resize/ResizeWorker.cs | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 35d1931d0..d94aeffe6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -61,9 +61,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The weighted sum [MethodImpl(InliningOptions.ShortMethod)] public Vector4 Convolve(Span rowSpan) - { - return this.ConvolveCore(ref rowSpan[this.StartIndex]); - } + => this.ConvolveCore(ref rowSpan[this.StartIndex]); [MethodImpl(InliningOptions.ShortMethod)] public Vector4 ConvolveCore(ref Vector4 rowStartRef) @@ -91,9 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] internal ResizeKernel AlterLeftValue(int left) - { - return new ResizeKernel(left, this.bufferPtr, this.Length); - } + => new ResizeKernel(left, this.bufferPtr, this.Length); internal void Fill(Span values) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 764349884..e7207c7e6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -105,14 +105,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public Span GetColumnSpan(int x, int startY) - { - return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); - } + => this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); public void Initialize() - { - this.CalculateFirstPassValues(this.currentWindow); - } + => this.CalculateFirstPassValues(this.currentWindow); public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) { From 1ea6a00338c5abb83bbc860a22f39c5e689a6b0e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 17 Dec 2020 21:16:26 +0000 Subject: [PATCH 04/28] intitial implementation --- .../ColorSpaces/Companding/SRgbCompanding.cs | 181 ++++++++++++++++-- src/ImageSharp/Common/Helpers/Numerics.cs | 38 ++++ 2 files changed, 198 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs index 9a8b5f0a8..865c28f3d 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -5,6 +5,10 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.ColorSpaces.Companding { @@ -18,21 +22,78 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// public static class SRgbCompanding { + private const int Length = Scale + 2; + private const int Scale = (1 << 14) - 1; + + private static readonly Lazy LazyCompressTable = new Lazy(() => + { + var result = new float[Length]; + + for (int i = 0; i < result.Length; i++) + { + double d = (double)i / Scale; + if (d <= (0.04045 / 12.92)) + { + d *= 12.92; + } + else + { + d = (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055; + } + + result[i] = (float)d; + } + + return result; + }); + + private static readonly Lazy LazyExpandTable = new Lazy(() => + { + var result = new float[Length]; + + for (int i = 0; i < result.Length; i++) + { + double d = (double)i / Scale; + if (d <= 0.04045) + { + d /= 12.92; + } + else + { + d = Math.Pow((d + 0.055) / 1.055, 2.4); + } + + result[i] = (float)d; + } + + return result; + }); + + private static readonly float[] ExpandTable = LazyExpandTable.Value; + private static readonly float[] CompressTable = LazyCompressTable.Value; + /// /// Expands the companded vectors to their linear equivalents with respect to the energy. /// /// The span of vectors. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Expand(Span vectors) { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); - - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported && vectors.Length >= 2) { - Expand(ref vectorsStart); + CompandAvx2(vectors, ExpandTable); - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); + if (Numerics.Modulo2(vectors.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + Expand(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1))); + } + } + else +#endif + { + CompandScalar(vectors, ExpandTable); } } @@ -40,17 +101,24 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy. /// /// The span of vectors. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Compress(Span vectors) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Compress(Span vectors) { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); - - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported && vectors.Length >= 2) { - Compress(ref vectorsStart); + CompandAvx2(vectors, CompressTable); - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); + if (Numerics.Modulo2(vectors.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + Compress(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1))); + } + } + else +#endif + { + CompandScalar(vectors, CompressTable); } } @@ -58,24 +126,26 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// Expands a companded vector to its linear equivalent with respect to the energy. /// /// The vector. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Expand(ref Vector4 vector) { vector.X = Expand(vector.X); vector.Y = Expand(vector.Y); vector.Z = Expand(vector.Z); + vector.W = Expand(vector.W); } /// /// Compresses an uncompanded vector (linear) to its nonlinear equivalent. /// /// The vector. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Compress(ref Vector4 vector) { vector.X = Compress(vector.X); vector.Y = Compress(vector.Y); vector.Z = Compress(vector.Z); + vector.W = Compress(vector.W); } /// @@ -83,15 +153,84 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// /// The channel value. /// The representing the linear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Expand(float channel) + => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); /// /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. /// /// The channel value. /// The representing the nonlinear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Compress(float channel) + => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; + +#if SUPPORTS_RUNTIME_INTRINSICS + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void CompandAvx2(Span vectors, float[] table) + { + fixed (float* tablePointer = &table[0]) + { + var scale = Vector256.Create((float)Scale); + Vector256 zero = Vector256.Zero; + var offset = Vector256.Create(1); + + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u)); + + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) + { + Vector256 multiplied = Avx.Multiply(scale, vectorsBase); + multiplied = Avx.Min(Avx.Max(zero, multiplied), scale); + + Vector256 truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied); + Vector256 truncatedF = Avx.ConvertToVector256Single(truncated); + + Vector256 low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float)); + Vector256 high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float)); + + vectorsBase = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); + } + } + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void CompandScalar(Span vectors, float[] table) + { + fixed (float* tablePointer = &table[0]) + { + Vector4 zero = Vector4.Zero; + var scale = new Vector4(Scale); + ref Vector4 vectorsBase = ref MemoryMarshal.GetReference(vectors); + ref Vector4 vectorsLast = ref Unsafe.Add(ref vectorsBase, vectors.Length); + + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) + { + Vector4 multiplied = Numerics.Clamp(vectorsBase * Scale, zero, scale); + + float f0 = multiplied.X; + float f1 = multiplied.Y; + float f2 = multiplied.Z; + float f3 = multiplied.W; + + uint i0 = (uint)f0; + uint i1 = (uint)f1; + uint i2 = (uint)f2; + uint i3 = (uint)f3; + + vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0); + vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1); + vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2); + vectorsBase.W = Numerics.Lerp(tablePointer[i3], tablePointer[i3 + 1], f3 - (int)i3); + + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); + } + } + } } } diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index b9ccfafe0..0ff8b3082 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -710,5 +710,43 @@ namespace SixLabors.ImageSharp } } } + +#if SUPPORTS_RUNTIME_INTRINSICS + + /// + /// Performs a linear interpolation between two values based on the given weighting. + /// + /// The first value. + /// The second value. + /// Values between 0 and 1 that indicates the weight of . + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Lerp( + in Vector256 value1, + in Vector256 value2, + in Vector256 amount) + { + Vector256 diff = Avx.Subtract(value2, value1); + if (Fma.IsSupported) + { + return Fma.MultiplyAdd(diff, amount, value1); + } + else + { + return Avx.Add(Avx.Multiply(diff, amount), value1); + } + } +#endif + + /// + /// Performs a linear interpolation between two values based on the given weighting. + /// + /// The first value. + /// The second value. + /// A value between 0 and 1 that indicates the weight of . + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Lerp(float value1, float value2, float amount) + => ((value2 - value1) * amount) + value1; } } From 94bc92b2369e89c1445029ce1accbf7718fa3ea2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 17 Dec 2020 23:56:07 +0000 Subject: [PATCH 05/28] Update tests to match real-world case. --- .../Processing/Resize.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Processing/Resize.cs b/tests/ImageSharp.Benchmarks/Processing/Resize.cs index 3823f7ec6..571c92cc8 100644 --- a/tests/ImageSharp.Benchmarks/Processing/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Resize.cs @@ -4,10 +4,13 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Globalization; +using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; +using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks { @@ -15,32 +18,35 @@ namespace SixLabors.ImageSharp.Benchmarks public abstract class Resize where TPixel : unmanaged, IPixel { + private byte[] bytes = null; + private Image sourceImage; - private Bitmap sourceBitmap; + private SDImage sourceBitmap; protected Configuration Configuration { get; } = new Configuration(new JpegConfigurationModule()); - [Params("3032-400")] - public virtual string SourceToDest { get; set; } - - protected int SourceSize { get; private set; } - protected int DestSize { get; private set; } [GlobalSetup] public virtual void Setup() { - string[] stuff = this.SourceToDest.Split('-'); - this.SourceSize = int.Parse(stuff[0], CultureInfo.InvariantCulture); - this.DestSize = int.Parse(stuff[1], CultureInfo.InvariantCulture); - this.sourceImage = new Image(this.Configuration, this.SourceSize, this.SourceSize); - this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize); + if (this.bytes is null) + { + this.bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Snake)); + + this.sourceImage = Image.Load(this.bytes); + + var ms1 = new MemoryStream(this.bytes); + this.sourceBitmap = SDImage.FromStream(ms1); + this.DestSize = this.sourceBitmap.Width / 2; + } } [GlobalCleanup] public void Cleanup() { + this.bytes = null; this.sourceImage.Dispose(); this.sourceBitmap.Dispose(); } @@ -127,9 +133,6 @@ namespace SixLabors.ImageSharp.Benchmarks [Params(128, 512, 1024, 8 * 1024)] public int WorkingBufferSizeHintInKilobytes { get; set; } - [Params("3032-400", "4000-300")] - public override string SourceToDest { get; set; } - public override void Setup() { this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024; From 016de298981308159044751ca15937494e46755b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Dec 2020 10:56:26 +0000 Subject: [PATCH 06/28] Fix alpha companding. --- .../ColorSpaces/Companding/SRgbCompanding.cs | 16 ++-- src/ImageSharp/Common/Helpers/Numerics.cs | 2 +- .../PixelFormats/Utils/Vector4Converters.cs | 4 +- .../PixelOperations/PixelOperationsTests.cs | 89 ++++++++----------- .../TestUtilities/ApproximateFloatComparer.cs | 32 +++++-- 5 files changed, 70 insertions(+), 73 deletions(-) diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs index 865c28f3d..e97d8bd28 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -22,8 +22,8 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// public static class SRgbCompanding { - private const int Length = Scale + 2; - private const int Scale = (1 << 14) - 1; + private const int Length = Scale + 2; // 256kb @ 16bit precision. + private const int Scale = (1 << 16) - 1; private static readonly Lazy LazyCompressTable = new Lazy(() => { @@ -129,10 +129,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Expand(ref Vector4 vector) { + // Alpha is already a linear representation of opacity so we do not want to convert it. vector.X = Expand(vector.X); vector.Y = Expand(vector.Y); vector.Z = Expand(vector.Z); - vector.W = Expand(vector.W); } /// @@ -142,10 +142,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Compress(ref Vector4 vector) { + // Alpha is already a linear representation of opacity so we do not want to convert it. vector.X = Compress(vector.X); vector.Y = Compress(vector.Y); vector.Z = Compress(vector.Z); - vector.W = Compress(vector.W); } /// @@ -192,7 +192,9 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding Vector256 low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float)); Vector256 high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float)); - vectorsBase = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); + // Alpha is already a linear representation of opacity so we do not want to convert it. + Vector256 companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); + vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl); vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } } @@ -216,17 +218,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding float f0 = multiplied.X; float f1 = multiplied.Y; float f2 = multiplied.Z; - float f3 = multiplied.W; uint i0 = (uint)f0; uint i1 = (uint)f1; uint i2 = (uint)f2; - uint i3 = (uint)f3; + // Alpha is already a linear representation of opacity so we do not want to convert it. vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0); vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1); vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2); - vectorsBase.W = Numerics.Lerp(tablePointer[i3], tablePointer[i3 + 1], f3 - (int)i3); vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 0ff8b3082..610542237 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp internal static class Numerics { #if SUPPORTS_RUNTIME_INTRINSICS - private const int BlendAlphaControl = 0b_10_00_10_00; + public const int BlendAlphaControl = 0b_10_00_10_00; private const int ShuffleAlphaControl = 0b_11_11_11_11; #endif diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs index acc2725ce..7af266276 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 39786a217..ebb974777 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations (s, d) => { Span destPixels = d.GetSpan(); - this.Operations.FromVector4Destructive(this.Configuration, (Span)s, destPixels, PixelConversionModifiers.Scale); + this.Operations.FromVector4Destructive(this.Configuration, s, destPixels, PixelConversionModifiers.Scale); }); } @@ -168,15 +168,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [MemberData(nameof(ArraySizesData))] public void FromCompandedScaledVector4(int count) { - void SourceAction(ref Vector4 v) - { - SRgbCompanding.Expand(ref v); - } + void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref v); - void ExpectedAction(ref Vector4 v) - { - SRgbCompanding.Compress(ref v); - } + void ExpectedAction(ref Vector4 v) => SRgbCompanding.Compress(ref v); Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -219,7 +213,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.None; this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); @@ -254,7 +249,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.None; this.Operations.FromVector4Destructive( @@ -297,7 +293,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.None; this.Operations.FromVector4Destructive( @@ -343,7 +340,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); - TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, (ReadOnlySpan)s, d.GetSpan())); + TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan())); } [Theory] @@ -356,11 +353,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => - { - Span destVectors = d.GetSpan(); - this.Operations.ToVector4(this.Configuration, (ReadOnlySpan)s, destVectors, PixelConversionModifiers.Scale); - }); + (s, d) => this.Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Scale)); } [Theory] @@ -369,13 +366,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - SRgbCompanding.Compress(ref v); } - void ExpectedAction(ref Vector4 v) - { - SRgbCompanding.Expand(ref v); - } + void ExpectedAction(ref Vector4 v) => SRgbCompanding.Expand(ref v); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -396,13 +389,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); } - void ExpectedAction(ref Vector4 v) - { - Numerics.Premultiply(ref v); - } + void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -419,13 +408,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); } - void ExpectedAction(ref Vector4 v) - { - Numerics.Premultiply(ref v); - } + void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -446,8 +431,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); - SRgbCompanding.Compress(ref v); } void ExpectedAction(ref Vector4 v) @@ -1006,15 +989,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Theory] [MemberData(nameof(ArraySizesData))] public void PackFromRgbPlanes(int count) - { - SimdUtilsTests.TestPackFromRgbPlanes( + => SimdUtilsTests.TestPackFromRgbPlanes( count, - ( - r, - g, - b, - actual) => PixelOperations.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual)); - } + (r, g, b, actual) => PixelOperations.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual)); public delegate void RefAction(ref T1 arg1); @@ -1071,7 +1048,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { - Vector4 v = GetVector(rnd); + Vector4 v = GetScaledVector(rnd); vectorModifier?.Invoke(ref v); result[i] = v; @@ -1088,7 +1065,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { - Vector4 v = GetVector(rnd); + Vector4 v = GetScaledVector(rnd); vectorModifier?.Invoke(ref v); @@ -1106,7 +1083,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { - Vector4 v = GetVector(rnd); + Vector4 v = GetScaledVector(rnd); vectorModifier?.Invoke(ref v); @@ -1129,10 +1106,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations return result; } - internal static Vector4 GetVector(Random rnd) - { - return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); - } + internal static Vector4 GetScaledVector(Random rnd) + => new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); [StructLayout(LayoutKind.Sequential)] internal unsafe struct OctetBytes @@ -1177,14 +1152,22 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); + var comparer = new ApproximateFloatComparer(0.0001F); - var comparer = new ApproximateFloatComparer(0.001f); for (int i = 0; i < count; i++) { - // ReSharper disable PossibleNullReferenceException Assert.Equal(expected[i], actual[i], comparer); + } + } + else if (typeof(IPixel).IsAssignableFrom(typeof(TDest))) + { + Span expected = this.ExpectedDestBuffer.AsSpan(); + Span actual = this.ActualDestBuffer.GetSpan(); + var comparer = new ApproximateFloatComparer(0.0001F); - // ReSharper restore PossibleNullReferenceException + for (int i = 0; i < count; i++) + { + Assert.Equal((IPixel)expected[i], (IPixel)actual[i], comparer); } } else diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index b2f390dcd..2a0ed19b4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests { @@ -12,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests internal readonly struct ApproximateFloatComparer : IEqualityComparer, IEqualityComparer, + IEqualityComparer, IEqualityComparer, IEqualityComparer { @@ -32,30 +34,42 @@ namespace SixLabors.ImageSharp.Tests } /// - public int GetHashCode(float obj) => obj.GetHashCode(); + public int GetHashCode(float obj) + => obj.GetHashCode(); /// - public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); + public bool Equals(Vector2 x, Vector2 y) + => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); /// - public int GetHashCode(Vector2 obj) => obj.GetHashCode(); + public int GetHashCode(Vector2 obj) + => obj.GetHashCode(); /// - public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W); + public bool Equals(IPixel x, IPixel y) + => this.Equals(x.ToScaledVector4(), y.ToScaledVector4()); + + public int GetHashCode(IPixel obj) + => obj.ToScaledVector4().GetHashCode(); + + /// + public bool Equals(Vector4 x, Vector4 y) + => this.Equals(x.X, y.X) + && this.Equals(x.Y, y.Y) + && this.Equals(x.Z, y.Z) + && this.Equals(x.W, y.W); /// - public int GetHashCode(Vector4 obj) => obj.GetHashCode(); + public int GetHashCode(Vector4 obj) + => obj.GetHashCode(); /// public bool Equals(ColorMatrix x, ColorMatrix y) - { - return - this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14) + => this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14) && this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24) && this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34) && this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44) && this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54); - } /// public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); From 933bb466cea8ee71e7daf07fdd485a15cf8621a4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Dec 2020 11:35:46 +0000 Subject: [PATCH 07/28] Alter 32bit test environment accuracy check. --- .../PixelFormats/PixelOperations/PixelOperationsTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index ebb974777..a6596d988 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -1152,7 +1152,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); - var comparer = new ApproximateFloatComparer(0.0001F); + var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); for (int i = 0; i < count; i++) { @@ -1163,7 +1163,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { Span expected = this.ExpectedDestBuffer.AsSpan(); Span actual = this.ActualDestBuffer.GetSpan(); - var comparer = new ApproximateFloatComparer(0.0001F); + var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); for (int i = 0; i < count; i++) { From bf9f25a14278d9e7129b65cfe75207fff7e5668d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Dec 2020 12:03:25 +0000 Subject: [PATCH 08/28] Use properties for true Lazy. --- src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs index e97d8bd28..4bc790493 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -69,8 +69,9 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding return result; }); - private static readonly float[] ExpandTable = LazyExpandTable.Value; - private static readonly float[] CompressTable = LazyCompressTable.Value; + private static float[] ExpandTable => LazyExpandTable.Value; + + private static float[] CompressTable => LazyCompressTable.Value; /// /// Expands the companded vectors to their linear equivalents with respect to the energy. From 4fa43bffd2f7b9220827b1c23ecd628a7c5e7412 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Dec 2020 15:39:39 +0000 Subject: [PATCH 09/28] Update PixelOperationsTests.cs --- .../PixelOperations/PixelOperationsTests.cs | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index a6596d988..cc7f32bef 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -182,7 +182,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale), + false); } [Theory] @@ -302,7 +303,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations s, d.GetSpan(), modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); - }); + }, + false); } [Theory] @@ -1030,11 +1032,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations internal static void TestOperation( TSource[] source, TDest[] expected, - Action> action) + Action> action, + bool preferExactComparison = true) where TSource : struct where TDest : struct { - using (var buffers = new TestBuffers(source, expected)) + using (var buffers = new TestBuffers(source, expected, preferExactComparison)) { action(buffers.SourceBuffer, buffers.ActualDestBuffer); buffers.Verify(); @@ -1135,11 +1138,14 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public TDest[] ExpectedDestBuffer { get; } - public TestBuffers(TSource[] source, TDest[] expectedDest) + public bool PreferExactComparison { get; } + + public TestBuffers(TSource[] source, TDest[] expectedDest, bool preferExactComparison = true) { this.SourceBuffer = source; this.ExpectedDestBuffer = expectedDest; this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate(expectedDest.Length); + this.PreferExactComparison = preferExactComparison; } public void Dispose() => this.ActualDestBuffer.Dispose(); @@ -1159,7 +1165,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(expected[i], actual[i], comparer); } } - else if (typeof(IPixel).IsAssignableFrom(typeof(TDest))) + else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel()) { Span expected = this.ExpectedDestBuffer.AsSpan(); Span actual = this.ActualDestBuffer.GetSpan(); @@ -1174,12 +1180,32 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { Span expected = this.ExpectedDestBuffer.AsSpan(); Span actual = this.ActualDestBuffer.GetSpan(); + for (int i = 0; i < count; i++) { Assert.Equal(expected[i], actual[i]); } } } + + // TODO: We really need a PixelTypeInfo.BitsPerComponent property!! + private static bool IsComplexPixel() + { + switch (default(TDest)) + { + case HalfSingle _: + case HalfVector2 _: + case L16 _: + case La32 _: + case NormalizedShort2 _: + case Rg32 _: + case Short2 _: + return true; + + default: + return Unsafe.SizeOf() > sizeof(int); + } + } } } } From 3cbd8e393d570ba53184b7386667ede3685248ba Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Dec 2020 15:58:31 +0000 Subject: [PATCH 10/28] Use explicit threadsafety declaration. --- .../ColorSpaces/Companding/SRgbCompanding.cs | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs index 4bc790493..dc6c960aa 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -25,49 +25,53 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding private const int Length = Scale + 2; // 256kb @ 16bit precision. private const int Scale = (1 << 16) - 1; - private static readonly Lazy LazyCompressTable = new Lazy(() => - { - var result = new float[Length]; - - for (int i = 0; i < result.Length; i++) + private static readonly Lazy LazyCompressTable = new Lazy( + () => { - double d = (double)i / Scale; - if (d <= (0.04045 / 12.92)) - { - d *= 12.92; - } - else + var result = new float[Length]; + + for (int i = 0; i < result.Length; i++) { - d = (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055; + double d = (double)i / Scale; + if (d <= (0.04045 / 12.92)) + { + d *= 12.92; + } + else + { + d = (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055; + } + + result[i] = (float)d; } - result[i] = (float)d; - } - - return result; - }); + return result; + }, + true); - private static readonly Lazy LazyExpandTable = new Lazy(() => - { - var result = new float[Length]; - - for (int i = 0; i < result.Length; i++) + private static readonly Lazy LazyExpandTable = new Lazy( + () => { - double d = (double)i / Scale; - if (d <= 0.04045) - { - d /= 12.92; - } - else + var result = new float[Length]; + + for (int i = 0; i < result.Length; i++) { - d = Math.Pow((d + 0.055) / 1.055, 2.4); + double d = (double)i / Scale; + if (d <= 0.04045) + { + d /= 12.92; + } + else + { + d = Math.Pow((d + 0.055) / 1.055, 2.4); + } + + result[i] = (float)d; } - result[i] = (float)d; - } - - return result; - }); + return result; + }, + true); private static float[] ExpandTable => LazyExpandTable.Value; From 0df047222b21ee2eeaa382513ec7c526e93bb37c Mon Sep 17 00:00:00 2001 From: Petar Tasev Date: Tue, 12 Jan 2021 20:15:02 -0800 Subject: [PATCH 11/28] Add PremultiplyAlpha to ResizeOptions --- .../Transforms/Resize/ResizeProcessor.cs | 6 +++++ .../Resize/ResizeProcessor{TPixel}.cs | 14 +++++++--- src/ImageSharp/Processing/ResizeOptions.cs | 6 +++++ .../Processors/Transforms/ResizeTests.cs | 26 +++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index 576f97a93..3d6900683 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -26,6 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.DestinationHeight = size.Height; this.DestinationRectangle = rectangle; this.Compand = options.Compand; + this.PremultiplyAlpha = options.PremultiplyAlpha; } /// @@ -53,6 +54,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public bool Compand { get; } + /// + /// Gets a value indicating whether to premultiply the alpha (if it exists) during the resize operation. + /// + public bool PremultiplyAlpha { get; } + /// public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => new ResizeProcessor(configuration, this, source, sourceRectangle); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 3c033d89e..642da8230 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -21,6 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly IResampler resampler; private readonly Rectangle destinationRectangle; private readonly bool compand; + private readonly bool premultiplyAlpha; private Image destination; public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) @@ -30,6 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destinationHeight = definition.DestinationHeight; this.destinationRectangle = definition.DestinationRectangle; this.resampler = definition.Sampler; + this.premultiplyAlpha = definition.PremultiplyAlpha; this.compand = definition.Compand; } @@ -60,6 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle sourceRectangle = this.SourceRectangle; Rectangle destinationRectangle = this.destinationRectangle; bool compand = this.compand; + bool premultiplyAlpha = this.premultiplyAlpha; // Handle resize dimensions identical to the original if (source.Width == destination.Width @@ -128,7 +131,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms sourceRectangle, destinationRectangle, interest, - compand); + compand, + premultiplyAlpha); } } @@ -168,10 +172,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle sourceRectangle, Rectangle destinationRectangle, Rectangle interest, - bool compand) + bool compand, + bool premultiplyAlpha) { - PixelConversionModifiers conversionModifiers = - PixelConversionModifiers.Premultiply.ApplyCompanding(compand); + PixelConversionModifiers conversionModifiers = premultiplyAlpha ? + PixelConversionModifiers.Premultiply.ApplyCompanding(compand) : + PixelConversionModifiers.None.ApplyCompanding(compand); Buffer2DRegion sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); diff --git a/src/ImageSharp/Processing/ResizeOptions.cs b/src/ImageSharp/Processing/ResizeOptions.cs index bad8bd455..4b31998da 100644 --- a/src/ImageSharp/Processing/ResizeOptions.cs +++ b/src/ImageSharp/Processing/ResizeOptions.cs @@ -45,5 +45,11 @@ namespace SixLabors.ImageSharp.Processing /// Gets or sets the target rectangle to resize into. /// public Rectangle? TargetRectangle { get; set; } + + /// + /// Gets or sets a value indicating whether to premultiply + /// the alpha (if it exists) during the resize operation. + /// + public bool PremultiplyAlpha { get; set; } = true; } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 4a20f4e56..f4a94782f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -216,6 +216,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms appendSourceFileOrDescription: false); } + [Theory] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)] + public void Resize_PremultiplyAlpha(TestImageProvider provider, bool premultiplyAlpha) + where TPixel : unmanaged, IPixel + { + string details = premultiplyAlpha ? "On" : "Off"; + + provider.RunValidatingProcessorTest( + x => + { + var resizeOptions = new ResizeOptions() + { + Size = x.GetCurrentSize() / 2, + Mode = ResizeMode.Crop, + Sampler = KnownResamplers.Bicubic, + Compand = false, + PremultiplyAlpha = premultiplyAlpha + }; + x.Resize(resizeOptions); + }, + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + [Theory] [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] public void Resize_IsAppliedToAllFrames(TestImageProvider provider) From 62a01b74755c5dde02e83a9051ebb9e29ce5a929 Mon Sep 17 00:00:00 2001 From: Petar Tasev Date: Wed, 13 Jan 2021 09:05:56 -0800 Subject: [PATCH 12/28] Split PixelConversionModifiers into a separate function. --- .../Transforms/Resize/ResizeProcessor{TPixel}.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 642da8230..ec238608e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -163,6 +163,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms in operation); } + private static PixelConversionModifiers GetModifiers(bool compand, bool premultiplyAlpha) + { + if (premultiplyAlpha) + { + return PixelConversionModifiers.Premultiply.ApplyCompanding(compand); + } + else + { + return PixelConversionModifiers.None.ApplyCompanding(compand); + } + } + private static void ApplyResizeFrameTransform( Configuration configuration, ImageFrame source, @@ -175,9 +187,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms bool compand, bool premultiplyAlpha) { - PixelConversionModifiers conversionModifiers = premultiplyAlpha ? - PixelConversionModifiers.Premultiply.ApplyCompanding(compand) : - PixelConversionModifiers.None.ApplyCompanding(compand); + PixelConversionModifiers conversionModifiers = GetModifiers(compand, premultiplyAlpha); Buffer2DRegion sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); From 8c77c494d6a3d62d71bbf9a449ad36132f3e388d Mon Sep 17 00:00:00 2001 From: Petar Tasev Date: Wed, 13 Jan 2021 14:29:54 -0800 Subject: [PATCH 13/28] Updated referenced image submodule to latest origin master. --- tests/Images/External | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Images/External b/tests/Images/External index 8b43d14d2..346070e5b 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 8b43d14d21ce9b436af3d12a70d38402cdba176b +Subproject commit 346070e5ba538f1a3bbafc0ea7367404c5f8c9ab From a968b289a0f33d2f67eff9608059c347b4b4dc59 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 14 Jan 2021 16:19:56 +0100 Subject: [PATCH 14/28] Use this.maxColors when getting size of the reduced palette, fixes #1505 --- .../Processors/Quantization/OctreeQuantizer{TPixel}.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index 6b64da1c9..700314f26 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -96,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex); // Length of reduced palette + transparency. - ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, this.maxColors)); this.pixelMap = new EuclideanPixelMap(this.Configuration, result); this.palette = result; From 94829b82fc8f262a221a6feec4e45f961ae60d56 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 14 Jan 2021 16:32:02 +0100 Subject: [PATCH 15/28] Add test case for #1505 --- .../Quantization/QuantizedImageTests.cs | 15 +++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../Gif/issues/issue1505_argumentoutofrange.png | 3 +++ 3 files changed, 19 insertions(+) create mode 100644 tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 7d57d8c49..6eb0f51a3 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -110,6 +110,21 @@ namespace SixLabors.ImageSharp.Tests } } + // Test case for issue: https://github.com/SixLabors/ImageSharp/issues/1505 + [Theory] + [WithFile(TestImages.Gif.Issues.Issue1505, PixelTypes.Rgba32)] + public void Issue1505(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + var octreeQuantizer = new OctreeQuantizer(); + IQuantizer quantizer = octreeQuantizer.CreatePixelSpecificQuantizer(Configuration.Default, new QuantizerOptions() { MaxColors = 128 }); + ImageFrame frame = image.Frames[0]; + quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); + } + } + private int GetTransparentIndex(IndexedImageFrame quantized) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d38ab80da..b5dd5b2bb 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -415,6 +415,7 @@ namespace SixLabors.ImageSharp.Tests public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif"; public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif"; public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif"; + public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png"; } public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; diff --git a/tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png b/tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png new file mode 100644 index 000000000..15dfa52a8 --- /dev/null +++ b/tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bea0faf09782d0d972e72ad94e53b8ca9c823fd3056fc6a97aba8c43105fcd66 +size 102581 From efd4d22665239b098aa1ede45231b6ed59586b64 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Sun, 17 Jan 2021 13:54:36 +0100 Subject: [PATCH 16/28] Add initial vectorized implementation with benchmarks --- ...bCrTables.cs => RgbToYCbCrConverterLut.cs} | 35 +++- .../Encoder/RgbToYCbCrConverterVectorized.cs | 182 ++++++++++++++++++ .../Encoder/YCbCrForwardConverter{TPixel}.cs | 28 ++- .../Encoder/YCbCrForwardConverterBenchmark.cs | 56 ++++++ 4 files changed, 278 insertions(+), 23 deletions(-) rename src/ImageSharp/Formats/Jpeg/Components/Encoder/{RgbToYCbCrTables.cs => RgbToYCbCrConverterLut.cs} (79%) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs create mode 100644 tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs similarity index 79% rename from src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs rename to src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs index 236eff27c..835a34f65 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs @@ -1,16 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { /// /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. /// Methods to build the tables are based on libjpeg implementation. - /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! /// - internal unsafe struct RgbToYCbCrTables + internal unsafe struct RgbToYCbCrConverterLut { /// /// The red luminance table @@ -63,10 +64,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Initializes the YCbCr tables /// - /// The initialized - public static RgbToYCbCrTables Create() + /// The initialized + public static RgbToYCbCrConverterLut Create() { - RgbToYCbCrTables tables = default; + RgbToYCbCrConverterLut tables = default; for (int i = 0; i <= 255; i++) { @@ -92,11 +93,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } /// - /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ConvertPixelInto( + private void ConvertPixelInto( int r, int g, int b, @@ -111,10 +111,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; - // float cr = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); + // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; } + public void Convert(Span rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) + { + ref Rgb24 rgbStart = ref rgbSpan[0]; + + for (int i = 0; i < 64; i++) + { + ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i); + + this.ConvertPixelInto( + c.R, + c.G, + c.B, + ref yBlock, + ref cbBlock, + ref crBlock, + i); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Fix(float x) => (int)((x * (1L << ScaleBits)) + 0.5F); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs new file mode 100644 index 000000000..068c3db96 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs @@ -0,0 +1,182 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal static class RgbToYCbCrConverterVectorized + { + private static ReadOnlySpan ExtractionMasks => new byte[] + { + 0x0, 0xFF, 0xFF, 0xFF, 0x1, 0xFF, 0xFF, 0xFF, 0x2, 0xFF, 0xFF, 0xFF, 0x3, 0xFF, 0xFF, 0xFF, 0x10, 0xFF, 0xFF, 0xFF, 0x11, 0xFF, 0xFF, 0xFF, 0x12, 0xFF, 0xFF, 0xFF, 0x13, 0xFF, 0xFF, 0xFF, + 0x4, 0xFF, 0xFF, 0xFF, 0x5, 0xFF, 0xFF, 0xFF, 0x6, 0xFF, 0xFF, 0xFF, 0x7, 0xFF, 0xFF, 0xFF, 0x14, 0xFF, 0xFF, 0xFF, 0x15, 0xFF, 0xFF, 0xFF, 0x16, 0xFF, 0xFF, 0xFF, 0x17, 0xFF, 0xFF, 0xFF, + 0x8, 0xFF, 0xFF, 0xFF, 0x9, 0xFF, 0xFF, 0xFF, 0xA, 0xFF, 0xFF, 0xFF, 0xB, 0xFF, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0xFF, 0x19, 0xFF, 0xFF, 0xFF, 0x1A, 0xFF, 0xFF, 0xFF, 0x1B, 0xFF, 0xFF, 0xFF, + 0xC, 0xFF, 0xFF, 0xFF, 0xD, 0xFF, 0xFF, 0xFF, 0xE, 0xFF, 0xFF, 0xFF, 0xF, 0xFF, 0xFF, 0xFF, 0x1C, 0xFF, 0xFF, 0xFF, 0x1D, 0xFF, 0xFF, 0xFF, 0x1E, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, + }; + + public static bool IsSupported + { + get + { +#if SUPPORTS_RUNTIME_INTRINSICS + return Avx2.IsSupported && Fma.IsSupported; +#else + return false; +#endif + } + } + + public static void Convert(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) + { + Debug.Assert(IsSupported, "AVX2 and FMA are required to run this converter"); + +#if SUPPORTS_RUNTIME_INTRINSICS + SeparateRgb(rgbSpan); + ConvertInternal(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); +#endif + } + +#if SUPPORTS_RUNTIME_INTRINSICS + /// + /// Rearranges the provided in-place + /// from { r00, g00, b00, ..., r63, g63, b63 } + /// to { r00, ... r31, g00, ..., g31, b00, ..., b31, + /// r32, ... r63, g32, ..., g63, b31, ..., b63 } + /// + /// + /// SSE is used for this operation as it is significantly faster than AVX in this specific case. + /// Solving this problem with AVX requires too many instructions that cross the 128-bit lanes of YMM registers. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void SeparateRgb(ReadOnlySpan rgbSpan) + { + var selectRed0 = Vector128.Create(0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + var selectRed1 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x05, 0x08, 0x0B, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + var selectRed2 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x04, 0x07, 0x0A, 0x0D); + + var selectGreen0 = Vector128.Create(0x01, 0x04, 0x07, 0x0A, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + var selectGreen1 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + var selectGreen2 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x05, 0x08, 0x0B, 0x0E); + + var selectBlue0 = Vector128.Create(0x02, 0x05, 0x08, 0x0B, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + var selectBlue1 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x04, 0x07, 0x0A, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + var selectBlue2 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F); + + for (int i = 0; i < 2; i++) + { + ref Vector128 inRef = ref Unsafe.Add(ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)), i * 6); + + Vector128 in0 = inRef; + Vector128 in1 = Unsafe.Add(ref inRef, 1); + Vector128 in2 = Unsafe.Add(ref inRef, 2); + + Vector128 r0 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectRed0), Ssse3.Shuffle(in1, selectRed1)), Ssse3.Shuffle(in2, selectRed2)); + Vector128 g0 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectGreen0), Ssse3.Shuffle(in1, selectGreen1)), Ssse3.Shuffle(in2, selectGreen2)); + Vector128 b0 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectBlue0), Ssse3.Shuffle(in1, selectBlue1)), Ssse3.Shuffle(in2, selectBlue2)); + + in0 = Unsafe.Add(ref inRef, 3); + in1 = Unsafe.Add(ref inRef, 4); + in2 = Unsafe.Add(ref inRef, 5); + + Vector128 r1 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectRed0), Ssse3.Shuffle(in1, selectRed1)), Ssse3.Shuffle(in2, selectRed2)); + Vector128 g1 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectGreen0), Ssse3.Shuffle(in1, selectGreen1)), Ssse3.Shuffle(in2, selectGreen2)); + Vector128 b1 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectBlue0), Ssse3.Shuffle(in1, selectBlue1)), Ssse3.Shuffle(in2, selectBlue2)); + + inRef = r0; + Unsafe.Add(ref inRef, 1) = r1; + Unsafe.Add(ref inRef, 2) = g0; + Unsafe.Add(ref inRef, 3) = g1; + Unsafe.Add(ref inRef, 4) = b0; + Unsafe.Add(ref inRef, 5) = b1; + } + } + + /// + /// Converts the previously separated (see ) RGB values to YCbCr using AVX2 and FMA. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void ConvertInternal(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) + { + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var f128 = Vector256.Create(128f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + + ref Vector256 inRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); + + for (int i = 0; i < 2; i++) + { + ref Vector256 destYRef = ref Unsafe.Add(ref Unsafe.As>(ref yBlock), i * 4); + ref Vector256 destCbRef = ref Unsafe.Add(ref Unsafe.As>(ref cbBlock), i * 4); + ref Vector256 destCrRef = ref Unsafe.Add(ref Unsafe.As>(ref crBlock), i * 4); + + Vector256 red = Unsafe.Add(ref inRef, i * 3); + Vector256 green = Unsafe.Add(ref inRef, (i * 3) + 1); + Vector256 blue = Unsafe.Add(ref inRef, (i * 3) + 2); + + for (int j = 0; j < 2; j++) + { + // 1st part of unrolled loop + Vector256 mask = Unsafe.Add(ref Unsafe.As>(ref MemoryMarshal.GetReference(ExtractionMasks)), j * 2); + + Vector256 r = Avx.ConvertToVector256Single(Avx2.Shuffle(red, mask).AsInt32()); + Vector256 g = Avx.ConvertToVector256Single(Avx2.Shuffle(green, mask).AsInt32()); + Vector256 b = Avx.ConvertToVector256Single(Avx2.Shuffle(blue, mask).AsInt32()); + + // (0.299F * r) + (0.587F * g) + (0.114F * b); + Vector256 yy0 = Fma.MultiplyAdd(f0299, r, Fma.MultiplyAdd(f0587, g, Avx.Multiply(f0114, b))); + + // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) + Vector256 cb0 = Avx.Add(f128, Fma.MultiplyAdd(fn0168736, r, Fma.MultiplyAdd(fn0331264, g, Avx.Multiply(f05, b)))); + + // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) + Vector256 cr0 = Avx.Add(f128, Fma.MultiplyAdd(f05, r, Fma.MultiplyAdd(fn0418688, g, Avx.Multiply(fn0081312F, b)))); + + // 2nd part of unrolled loop + mask = Unsafe.Add(ref Unsafe.As>(ref MemoryMarshal.GetReference(ExtractionMasks)), (j * 2) + 1); + + r = Avx.ConvertToVector256Single(Avx2.Shuffle(red, mask).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.Shuffle(green, mask).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.Shuffle(blue, mask).AsInt32()); + + // (0.299F * r) + (0.587F * g) + (0.114F * b); + Vector256 yy1 = Fma.MultiplyAdd(f0299, r, Fma.MultiplyAdd(f0587, g, Avx.Multiply(f0114, b))); + + // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) + Vector256 cb1 = Avx.Add(f128, Fma.MultiplyAdd(fn0168736, r, Fma.MultiplyAdd(fn0331264, g, Avx.Multiply(f05, b)))); + + // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) + Vector256 cr1 = Avx.Add(f128, Fma.MultiplyAdd(f05, r, Fma.MultiplyAdd(fn0418688, g, Avx.Multiply(fn0081312F, b)))); + + // store results from 1st and 2nd part + Vector256 tmpY = Avx.Permute2x128(yy0, yy1, 0b0010_0001); + Unsafe.Add(ref destYRef, j) = Avx.Blend(yy0, tmpY, 0b1111_0000); + Unsafe.Add(ref destYRef, j + 2) = Avx.Blend(yy1, tmpY, 0b0000_1111); + + Vector256 tmpCb = Avx.Permute2x128(cb0, cb1, 0b0010_0001); + Unsafe.Add(ref destCbRef, j) = Avx.Blend(cb0, tmpCb, 0b1111_0000); + Unsafe.Add(ref destCbRef, j + 2) = Avx.Blend(cb0, tmpCb, 0b0000_1111); + + Vector256 tmpCr = Avx.Permute2x128(cr0, cr1, 0b0010_0001); + Unsafe.Add(ref destCrRef, j) = Avx.Blend(cr0, tmpCr, 0b1111_0000); + Unsafe.Add(ref destCrRef, j + 2) = Avx.Blend(cr0, tmpCr, 0b0000_1111); + } + } + } +#endif + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index 4d6186e22..b65899327 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -33,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// The color conversion tables /// - private RgbToYCbCrTables colorTables; + private RgbToYCbCrConverterLut colorTables; /// /// Temporal 8x8 block to hold TPixel data @@ -48,7 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public static YCbCrForwardConverter Create() { var result = default(YCbCrForwardConverter); - result.colorTables = RgbToYCbCrTables.Create(); + if (RgbToYCbCrConverterVectorized.IsSupported) + { + // Avoid creating lookup tables, when vectorized converter is supported + result.colorTables = RgbToYCbCrConverterLut.Create(); + } + return result; } @@ -65,20 +69,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder ref Block8x8F yBlock = ref this.Y; ref Block8x8F cbBlock = ref this.Cb; ref Block8x8F crBlock = ref this.Cr; - ref Rgb24 rgbStart = ref rgbSpan[0]; - for (int i = 0; i < 64; i++) + if (RgbToYCbCrConverterVectorized.IsSupported) { - ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i); - - this.colorTables.ConvertPixelInto( - c.R, - c.G, - c.B, - ref yBlock, - ref cbBlock, - ref crBlock, - i); + RgbToYCbCrConverterVectorized.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); + } + else + { + this.colorTables.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); } } } diff --git a/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs b/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs new file mode 100644 index 000000000..1db407293 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder +{ + public class YCbCrForwardConverterBenchmark + { + private RgbToYCbCrConverterLut converter; + private Rgb24[] data; + + [GlobalSetup] + public void Setup() + { + this.converter = RgbToYCbCrConverterLut.Create(); + + var r = new Random(42); + this.data = new Rgb24[64]; + + var d = new byte[3]; + for (int i = 0; i < this.data.Length; i++) + { + r.NextBytes(d); + this.data[i] = new Rgb24(d[0], d[1], d[2]); + } + } + + [Benchmark(Baseline = true)] + public void ConvertLut() + { + Block8x8F y = default; + Block8x8F cb = default; + Block8x8F cr = default; + + this.converter.Convert(this.data.AsSpan(), ref y, ref cb, ref cr); + } + + [Benchmark] + public void ConvertVectorized() + { + Block8x8F y = default; + Block8x8F cb = default; + Block8x8F cr = default; + + if (RgbToYCbCrConverterVectorized.IsSupported) + { + RgbToYCbCrConverterVectorized.Convert(this.data.AsSpan(), ref y, ref cb, ref cr); + } + } + } +} From 429696bd5e0ae1a5a872d8711c305228799726f9 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Sun, 17 Jan 2021 14:55:21 +0100 Subject: [PATCH 17/28] Fix mistakes in final touches --- .../Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs | 4 ++-- .../Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs index 068c3db96..ddaa2069e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs @@ -169,11 +169,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder Vector256 tmpCb = Avx.Permute2x128(cb0, cb1, 0b0010_0001); Unsafe.Add(ref destCbRef, j) = Avx.Blend(cb0, tmpCb, 0b1111_0000); - Unsafe.Add(ref destCbRef, j + 2) = Avx.Blend(cb0, tmpCb, 0b0000_1111); + Unsafe.Add(ref destCbRef, j + 2) = Avx.Blend(cb1, tmpCb, 0b0000_1111); Vector256 tmpCr = Avx.Permute2x128(cr0, cr1, 0b0010_0001); Unsafe.Add(ref destCrRef, j) = Avx.Blend(cr0, tmpCr, 0b1111_0000); - Unsafe.Add(ref destCrRef, j + 2) = Avx.Blend(cr0, tmpCr, 0b0000_1111); + Unsafe.Add(ref destCrRef, j + 2) = Avx.Blend(cr1, tmpCr, 0b0000_1111); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index b65899327..8fcc63c6a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public static YCbCrForwardConverter Create() { var result = default(YCbCrForwardConverter); - if (RgbToYCbCrConverterVectorized.IsSupported) + if (!RgbToYCbCrConverterVectorized.IsSupported) { // Avoid creating lookup tables, when vectorized converter is supported result.colorTables = RgbToYCbCrConverterLut.Create(); From 93099d1585e14706f85ea58682d799d4b446b8e4 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Sun, 17 Jan 2021 22:50:32 +0100 Subject: [PATCH 18/28] Add unit tests for both converters --- .../Encoder/RgbToYCbCrConverterLut.cs | 2 +- .../Formats/Jpg/RgbToYCbCrConverterTests.cs | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs index 835a34f65..3c1a02c5a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; - // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) + // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs new file mode 100644 index 000000000..9134de42e --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; +using Xunit; +using Xunit.Abstractions; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class RgbToYCbCrConverterTests + { + private const float Epsilon = .5F; + private static readonly ApproximateColorSpaceComparer Comparer = new ApproximateColorSpaceComparer(Epsilon); + + public RgbToYCbCrConverterTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + [Fact] + public void TestLutConverter() + { + Rgb24[] data = CreateTestData(); + var target = RgbToYCbCrConverterLut.Create(); + + Block8x8F y = default; + Block8x8F cb = default; + Block8x8F cr = default; + + target.Convert(data.AsSpan(), ref y, ref cb, ref cr); + + Verify(data, ref y, ref cb, ref cr); + } + + [Fact] + public void TestVectorizedConverter() + { + if (!RgbToYCbCrConverterVectorized.IsSupported) + { + this.Output.WriteLine("No AVX and/or FMA present, skipping test!"); + return; + } + + Rgb24[] data = CreateTestData(); + + // RgbToYCbCrConverterVectorized uses `data` as working memory so we need a copy for verification below + Rgb24[] dataCopy = new Rgb24[data.Length]; + data.CopyTo(dataCopy, 0); + + Block8x8F y = default; + Block8x8F cb = default; + Block8x8F cr = default; + + RgbToYCbCrConverterVectorized.Convert(data.AsSpan(), ref y, ref cb, ref cr); + + Verify(dataCopy, ref y, ref cb, ref cr); + } + + private static void Verify(ReadOnlySpan data, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult) + { + for (int i = 0; i < data.Length; i++) + { + int r = data[i].R; + int g = data[i].G; + int b = data[i].B; + + float y = (0.299F * r) + (0.587F * g) + (0.114F * b); + float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); + float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); + + Assert.Equal(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i]), Comparer); + } + } + + private static Rgb24[] CreateTestData() + { + var data = new Rgb24[64]; + var r = new Random(); + + var random = new byte[3]; + for (int i = 0; i < data.Length; i++) + { + r.NextBytes(random); + data[i] = new Rgb24(random[0], random[1], random[2]); + } + + return data; + } + } +} From 08a68af1a997c56a4e6a721cf9de7fdb1cd1f4ce Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Sun, 17 Jan 2021 23:01:18 +0100 Subject: [PATCH 19/28] Allow epsilon of 1F for existing LUT converter --- .../Formats/Jpg/RgbToYCbCrConverterTests.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs index 9134de42e..776cbb44f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs @@ -15,9 +15,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public class RgbToYCbCrConverterTests { - private const float Epsilon = .5F; - private static readonly ApproximateColorSpaceComparer Comparer = new ApproximateColorSpaceComparer(Epsilon); - public RgbToYCbCrConverterTests(ITestOutputHelper output) { this.Output = output; @@ -37,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg target.Convert(data.AsSpan(), ref y, ref cb, ref cr); - Verify(data, ref y, ref cb, ref cr); + Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F)); } [Fact] @@ -61,10 +58,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg RgbToYCbCrConverterVectorized.Convert(data.AsSpan(), ref y, ref cb, ref cr); - Verify(dataCopy, ref y, ref cb, ref cr); + Verify(dataCopy, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); } - private static void Verify(ReadOnlySpan data, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult) + private static void Verify(ReadOnlySpan data, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult, ApproximateColorSpaceComparer comparer) { for (int i = 0; i < data.Length; i++) { @@ -76,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - Assert.Equal(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i]), Comparer); + Assert.Equal(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i]), comparer); } } From 5033e3eb950aa15a89a1ccd1f706c629344f9119 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Mon, 18 Jan 2021 12:49:08 +0100 Subject: [PATCH 20/28] Improve algorithm --- .../Encoder/RgbToYCbCrConverterVectorized.cs | 180 ++++++------------ .../Formats/Jpg/RgbToYCbCrConverterTests.cs | 8 +- 2 files changed, 61 insertions(+), 127 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs index ddaa2069e..209cc3c6a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs @@ -15,97 +15,43 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { internal static class RgbToYCbCrConverterVectorized { - private static ReadOnlySpan ExtractionMasks => new byte[] - { - 0x0, 0xFF, 0xFF, 0xFF, 0x1, 0xFF, 0xFF, 0xFF, 0x2, 0xFF, 0xFF, 0xFF, 0x3, 0xFF, 0xFF, 0xFF, 0x10, 0xFF, 0xFF, 0xFF, 0x11, 0xFF, 0xFF, 0xFF, 0x12, 0xFF, 0xFF, 0xFF, 0x13, 0xFF, 0xFF, 0xFF, - 0x4, 0xFF, 0xFF, 0xFF, 0x5, 0xFF, 0xFF, 0xFF, 0x6, 0xFF, 0xFF, 0xFF, 0x7, 0xFF, 0xFF, 0xFF, 0x14, 0xFF, 0xFF, 0xFF, 0x15, 0xFF, 0xFF, 0xFF, 0x16, 0xFF, 0xFF, 0xFF, 0x17, 0xFF, 0xFF, 0xFF, - 0x8, 0xFF, 0xFF, 0xFF, 0x9, 0xFF, 0xFF, 0xFF, 0xA, 0xFF, 0xFF, 0xFF, 0xB, 0xFF, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0xFF, 0x19, 0xFF, 0xFF, 0xFF, 0x1A, 0xFF, 0xFF, 0xFF, 0x1B, 0xFF, 0xFF, 0xFF, - 0xC, 0xFF, 0xFF, 0xFF, 0xD, 0xFF, 0xFF, 0xFF, 0xE, 0xFF, 0xFF, 0xFF, 0xF, 0xFF, 0xFF, 0xFF, 0x1C, 0xFF, 0xFF, 0xFF, 0x1D, 0xFF, 0xFF, 0xFF, 0x1E, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, - }; - public static bool IsSupported { get { #if SUPPORTS_RUNTIME_INTRINSICS - return Avx2.IsSupported && Fma.IsSupported; + return Avx2.IsSupported; #else return false; #endif } } - public static void Convert(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) - { - Debug.Assert(IsSupported, "AVX2 and FMA are required to run this converter"); - #if SUPPORTS_RUNTIME_INTRINSICS - SeparateRgb(rgbSpan); - ConvertInternal(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); -#endif - } - -#if SUPPORTS_RUNTIME_INTRINSICS - /// - /// Rearranges the provided in-place - /// from { r00, g00, b00, ..., r63, g63, b63 } - /// to { r00, ... r31, g00, ..., g31, b00, ..., b31, - /// r32, ... r63, g32, ..., g63, b31, ..., b63 } - /// - /// - /// SSE is used for this operation as it is significantly faster than AVX in this specific case. - /// Solving this problem with AVX requires too many instructions that cross the 128-bit lanes of YMM registers. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static void SeparateRgb(ReadOnlySpan rgbSpan) + private static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] { - var selectRed0 = Vector128.Create(0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); - var selectRed1 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x05, 0x08, 0x0B, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); - var selectRed2 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x04, 0x07, 0x0A, 0x0D); - - var selectGreen0 = Vector128.Create(0x01, 0x04, 0x07, 0x0A, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); - var selectGreen1 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); - var selectGreen2 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x05, 0x08, 0x0B, 0x0E); + 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, + 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 + }; - var selectBlue0 = Vector128.Create(0x02, 0x05, 0x08, 0x0B, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); - var selectBlue1 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x04, 0x07, 0x0A, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); - var selectBlue2 = Vector128.Create(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F); + private static ReadOnlySpan MoveLast24BytesToSeparateLanes => new byte[] + { + 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0 + }; - for (int i = 0; i < 2; i++) - { - ref Vector128 inRef = ref Unsafe.Add(ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)), i * 6); - - Vector128 in0 = inRef; - Vector128 in1 = Unsafe.Add(ref inRef, 1); - Vector128 in2 = Unsafe.Add(ref inRef, 2); - - Vector128 r0 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectRed0), Ssse3.Shuffle(in1, selectRed1)), Ssse3.Shuffle(in2, selectRed2)); - Vector128 g0 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectGreen0), Ssse3.Shuffle(in1, selectGreen1)), Ssse3.Shuffle(in2, selectGreen2)); - Vector128 b0 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectBlue0), Ssse3.Shuffle(in1, selectBlue1)), Ssse3.Shuffle(in2, selectBlue2)); - - in0 = Unsafe.Add(ref inRef, 3); - in1 = Unsafe.Add(ref inRef, 4); - in2 = Unsafe.Add(ref inRef, 5); - - Vector128 r1 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectRed0), Ssse3.Shuffle(in1, selectRed1)), Ssse3.Shuffle(in2, selectRed2)); - Vector128 g1 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectGreen0), Ssse3.Shuffle(in1, selectGreen1)), Ssse3.Shuffle(in2, selectGreen2)); - Vector128 b1 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(in0, selectBlue0), Ssse3.Shuffle(in1, selectBlue1)), Ssse3.Shuffle(in2, selectBlue2)); - - inRef = r0; - Unsafe.Add(ref inRef, 1) = r1; - Unsafe.Add(ref inRef, 2) = g0; - Unsafe.Add(ref inRef, 3) = g1; - Unsafe.Add(ref inRef, 4) = b0; - Unsafe.Add(ref inRef, 5) = b1; - } - } + private static ReadOnlySpan ExtractRgb => new byte[] + { + 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF + }; +#endif - /// - /// Converts the previously separated (see ) RGB values to YCbCr using AVX2 and FMA. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static void ConvertInternal(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) + public static void Convert(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) { + Debug.Assert(IsSupported, "AVX2 is required to run this converter"); + +#if SUPPORTS_RUNTIME_INTRINSICS var f0299 = Vector256.Create(0.299f); var f0587 = Vector256.Create(0.587f); var f0114 = Vector256.Create(0.114f); @@ -115,68 +61,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder var fn0418688 = Vector256.Create(-0.418688f); var fn0081312F = Vector256.Create(-0.081312F); var f05 = Vector256.Create(0.5f); + var zero = Vector256.Create(0).AsByte(); ref Vector256 inRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); - - for (int i = 0; i < 2; i++) + ref Vector256 destYRef = ref Unsafe.As>(ref yBlock); + ref Vector256 destCbRef = ref Unsafe.As>(ref cbBlock); + ref Vector256 destCrRef = ref Unsafe.As>(ref crBlock); + + var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); + var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); + Vector256 rgb, rg, bx; + Vector256 r, g, b; + for (int i = 0; i < 7; i++) { - ref Vector256 destYRef = ref Unsafe.Add(ref Unsafe.As>(ref yBlock), i * 4); - ref Vector256 destCbRef = ref Unsafe.Add(ref Unsafe.As>(ref cbBlock), i * 4); - ref Vector256 destCrRef = ref Unsafe.Add(ref Unsafe.As>(ref crBlock), i * 4); - - Vector256 red = Unsafe.Add(ref inRef, i * 3); - Vector256 green = Unsafe.Add(ref inRef, (i * 3) + 1); - Vector256 blue = Unsafe.Add(ref inRef, (i * 3) + 2); + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)(24 * i)).AsUInt32(), extractToLanesMask).AsByte(); - for (int j = 0; j < 2; j++) - { - // 1st part of unrolled loop - Vector256 mask = Unsafe.Add(ref Unsafe.As>(ref MemoryMarshal.GetReference(ExtractionMasks)), j * 2); + rgb = Avx2.Shuffle(rgb, extractRgbMask); - Vector256 r = Avx.ConvertToVector256Single(Avx2.Shuffle(red, mask).AsInt32()); - Vector256 g = Avx.ConvertToVector256Single(Avx2.Shuffle(green, mask).AsInt32()); - Vector256 b = Avx.ConvertToVector256Single(Avx2.Shuffle(blue, mask).AsInt32()); + rg = Avx2.UnpackLow(rgb, zero); + bx = Avx2.UnpackHigh(rgb, zero); - // (0.299F * r) + (0.587F * g) + (0.114F * b); - Vector256 yy0 = Fma.MultiplyAdd(f0299, r, Fma.MultiplyAdd(f0587, g, Avx.Multiply(f0114, b))); + r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) - Vector256 cb0 = Avx.Add(f128, Fma.MultiplyAdd(fn0168736, r, Fma.MultiplyAdd(fn0331264, g, Avx.Multiply(f05, b)))); + // (0.299F * r) + (0.587F * g) + (0.114F * b); + Unsafe.Add(ref destYRef, i) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) - Vector256 cr0 = Avx.Add(f128, Fma.MultiplyAdd(f05, r, Fma.MultiplyAdd(fn0418688, g, Avx.Multiply(fn0081312F, b)))); + // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) + Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); - // 2nd part of unrolled loop - mask = Unsafe.Add(ref Unsafe.As>(ref MemoryMarshal.GetReference(ExtractionMasks)), (j * 2) + 1); - - r = Avx.ConvertToVector256Single(Avx2.Shuffle(red, mask).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.Shuffle(green, mask).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.Shuffle(blue, mask).AsInt32()); + // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) + Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + } - // (0.299F * r) + (0.587F * g) + (0.114F * b); - Vector256 yy1 = Fma.MultiplyAdd(f0299, r, Fma.MultiplyAdd(f0587, g, Avx.Multiply(f0114, b))); + extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveLast24BytesToSeparateLanes)); + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)160).AsUInt32(), extractToLanesMask).AsByte(); + rgb = Avx2.Shuffle(rgb, extractRgbMask); - // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) - Vector256 cb1 = Avx.Add(f128, Fma.MultiplyAdd(fn0168736, r, Fma.MultiplyAdd(fn0331264, g, Avx.Multiply(f05, b)))); + rg = Avx2.UnpackLow(rgb, zero); + bx = Avx2.UnpackHigh(rgb, zero); - // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) - Vector256 cr1 = Avx.Add(f128, Fma.MultiplyAdd(f05, r, Fma.MultiplyAdd(fn0418688, g, Avx.Multiply(fn0081312F, b)))); + r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - // store results from 1st and 2nd part - Vector256 tmpY = Avx.Permute2x128(yy0, yy1, 0b0010_0001); - Unsafe.Add(ref destYRef, j) = Avx.Blend(yy0, tmpY, 0b1111_0000); - Unsafe.Add(ref destYRef, j + 2) = Avx.Blend(yy1, tmpY, 0b0000_1111); + // (0.299F * r) + (0.587F * g) + (0.114F * b); + Unsafe.Add(ref destYRef, 7) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - Vector256 tmpCb = Avx.Permute2x128(cb0, cb1, 0b0010_0001); - Unsafe.Add(ref destCbRef, j) = Avx.Blend(cb0, tmpCb, 0b1111_0000); - Unsafe.Add(ref destCbRef, j + 2) = Avx.Blend(cb1, tmpCb, 0b0000_1111); + // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) + Unsafe.Add(ref destCbRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); - Vector256 tmpCr = Avx.Permute2x128(cr0, cr1, 0b0010_0001); - Unsafe.Add(ref destCrRef, j) = Avx.Blend(cr0, tmpCr, 0b1111_0000); - Unsafe.Add(ref destCrRef, j + 2) = Avx.Blend(cr1, tmpCr, 0b0000_1111); - } - } - } + // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) + Unsafe.Add(ref destCrRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); #endif + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs index 776cbb44f..9a6fc8d6f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs @@ -48,17 +48,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Rgb24[] data = CreateTestData(); - // RgbToYCbCrConverterVectorized uses `data` as working memory so we need a copy for verification below - Rgb24[] dataCopy = new Rgb24[data.Length]; - data.CopyTo(dataCopy, 0); - Block8x8F y = default; Block8x8F cb = default; Block8x8F cr = default; RgbToYCbCrConverterVectorized.Convert(data.AsSpan(), ref y, ref cb, ref cr); - Verify(dataCopy, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); + Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); } private static void Verify(ReadOnlySpan data, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult, ApproximateColorSpaceComparer comparer) @@ -73,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - Assert.Equal(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i]), comparer); + Assert.True(comparer.Equals(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y} == {yResult[i]}, {cb} == {cbResult[i]}, {cr} == {crResult[i]}"); } } From 1033297a37519b56729b7a5ba54259ba1fcb4de4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Jan 2021 18:19:31 +0100 Subject: [PATCH 21/28] Add initial FMA resize kernel convolve implementation --- .../Transforms/Resize/ResizeKernel.cs | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index d94aeffe6..bff2c574a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -4,6 +4,10 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.Processing.Processors.Transforms { @@ -66,21 +70,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public Vector4 ConvolveCore(ref Vector4 rowStartRef) { - ref float horizontalValues = ref Unsafe.AsRef(this.bufferPtr); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Fma.IsSupported) + { + float* bufferStart = this.bufferPtr; + float* bufferEnd = bufferStart + (this.Length & ~1); + Vector256 result256 = Vector256.Zero; - // Destination color components - Vector4 result = Vector4.Zero; + while (bufferStart < bufferEnd) + { + Vector256 rowItem256 = Unsafe.As>(ref rowStartRef); + var bufferItem256 = Vector256.Create(Vector128.Create(bufferStart[0]), Vector128.Create(bufferStart[1])); - for (int i = 0; i < this.Length; i++) - { - float weight = Unsafe.Add(ref horizontalValues, i); + result256 = Fma.MultiplyAdd(rowItem256, bufferItem256, result256); + + bufferStart += 2; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); + } + + Vector128 result128 = Sse.Add(result256.GetLower(), result256.GetUpper()); + + if ((this.Length & 1) != 0) + { + Vector128 rowItem128 = Unsafe.As>(ref rowStartRef); + var bufferItem128 = Vector128.Create(*bufferStart); - // Vector4 v = offsetedRowSpan[i]; - Vector4 v = Unsafe.Add(ref rowStartRef, i); - result += v * weight; + result128 = Fma.MultiplyAdd(rowItem128, bufferItem128, result128); + } + + return *(Vector4*)&result128; } + else +#endif + { + // Destination color components + Vector4 result = Vector4.Zero; + float* bufferStart = this.bufferPtr; + float* bufferEnd = this.bufferPtr + this.Length; + + while (bufferStart < bufferEnd) + { + // Vector4 v = offsetedRowSpan[i]; + result += rowStartRef * *bufferStart; - return result; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 1); + bufferStart++; + } + + return result; + } } /// From c825eccd10f14eb733cdbe4c75656005afae5aed Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Jan 2021 19:14:51 +0100 Subject: [PATCH 22/28] Improved loading of factors using permutation Assembly for loading in the loop went from: ```asm vmovss xmm2, [rax] vbroadcastss xmm2, xmm2 vmovss xmm3, [rax+4] vbroadcastss xmm3, xmm3 vinsertf128 ymm2, ymm2, xmm3, 1 ``` To: ```asm vmovsd xmm3, [rax] vbroadcastsd ymm3, xmm3 vpermps ymm3, ymm1, ymm3 ``` --- .../Processing/Processors/Transforms/Resize/ResizeKernel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index bff2c574a..02027f42d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -76,11 +76,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float* bufferStart = this.bufferPtr; float* bufferEnd = bufferStart + (this.Length & ~1); Vector256 result256 = Vector256.Zero; + var mask = Vector256.Create(0, 0, 0, 0, 1, 1, 1, 1); while (bufferStart < bufferEnd) { Vector256 rowItem256 = Unsafe.As>(ref rowStartRef); - var bufferItem256 = Vector256.Create(Vector128.Create(bufferStart[0]), Vector128.Create(bufferStart[1])); + Vector256 bufferItem256 = Avx2.PermuteVar8x32(Vector256.Create(*(double*)bufferStart).AsSingle(), mask); result256 = Fma.MultiplyAdd(rowItem256, bufferItem256, result256); From 1169e73915d98590e82d64f72fa3c2197e00aea9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Jan 2021 19:33:15 +0100 Subject: [PATCH 23/28] Switch from FMA to AVX2 instructions --- .../Processors/Transforms/Resize/ResizeKernel.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 02027f42d..5a87d045e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public Vector4 ConvolveCore(ref Vector4 rowStartRef) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Fma.IsSupported) + if (Avx2.IsSupported) { float* bufferStart = this.bufferPtr; float* bufferEnd = bufferStart + (this.Length & ~1); @@ -82,8 +82,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Vector256 rowItem256 = Unsafe.As>(ref rowStartRef); Vector256 bufferItem256 = Avx2.PermuteVar8x32(Vector256.Create(*(double*)bufferStart).AsSingle(), mask); + Vector256 multiply256 = Avx.Multiply(rowItem256, bufferItem256); - result256 = Fma.MultiplyAdd(rowItem256, bufferItem256, result256); + result256 = Avx.Add(multiply256, result256); bufferStart += 2; rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); @@ -95,8 +96,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Vector128 rowItem128 = Unsafe.As>(ref rowStartRef); var bufferItem128 = Vector128.Create(*bufferStart); + Vector128 multiply128 = Sse.Multiply(rowItem128, bufferItem128); - result128 = Fma.MultiplyAdd(rowItem128, bufferItem128, result128); + result128 = Sse.Add(multiply128, result128); } return *(Vector4*)&result128; @@ -114,8 +116,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Vector4 v = offsetedRowSpan[i]; result += rowStartRef * *bufferStart; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 1); bufferStart++; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 1); } return result; From 0e465cd8c30713b1c3c91966ebef855d4eda314d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Jan 2021 22:58:43 +0100 Subject: [PATCH 24/28] Revert to FMA, codegen improvements --- .../Transforms/Resize/ResizeKernel.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 5a87d045e..bd22864bb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public Vector4 ConvolveCore(ref Vector4 rowStartRef) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Avx2.IsSupported) + if (Fma.IsSupported) { float* bufferStart = this.bufferPtr; float* bufferEnd = bufferStart + (this.Length & ~1); @@ -80,11 +80,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms while (bufferStart < bufferEnd) { - Vector256 rowItem256 = Unsafe.As>(ref rowStartRef); - Vector256 bufferItem256 = Avx2.PermuteVar8x32(Vector256.Create(*(double*)bufferStart).AsSingle(), mask); - Vector256 multiply256 = Avx.Multiply(rowItem256, bufferItem256); - - result256 = Avx.Add(multiply256, result256); + // It is important to use a single expression here so that the JIT will correctly use vfmadd231ps + // for the FMA operation, and execute it directly on the target register and reading directly from + // memory for the first parameter. This skips initializing a SIMD register, and an extra copy. + // The code below should compile in the following assembly on .NET 5 x64: + // + // vmovsd xmm2, [rax] ; load *(double*)bufferStart into xmm2 as [ab, _] + // vpermps ymm2, ymm1, ymm2 ; permute as a float YMM register to [a, a, a, a, b, b, b, b] + // vfmadd231ps ymm0, ymm2, [r8] ; result256 = FMA(pixels, factors) + result256 + // + // For tracking the codegen issue with FMA, see: https://github.com/dotnet/runtime/issues/12212. + result256 = Fma.MultiplyAdd( + Unsafe.As>(ref rowStartRef), + Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), + result256); bufferStart += 2; rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); @@ -94,11 +103,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if ((this.Length & 1) != 0) { - Vector128 rowItem128 = Unsafe.As>(ref rowStartRef); - var bufferItem128 = Vector128.Create(*bufferStart); - Vector128 multiply128 = Sse.Multiply(rowItem128, bufferItem128); - - result128 = Sse.Add(multiply128, result128); + result128 = Fma.MultiplyAdd( + Unsafe.As>(ref rowStartRef), + Vector128.Create(*bufferStart), + result128); } return *(Vector4*)&result128; From e0b2defde22343414ee70babe21d1209fb760cbe Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Jan 2021 23:25:47 +0100 Subject: [PATCH 25/28] Add unrolled FMA loop --- .../Transforms/Resize/ResizeKernel.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index bd22864bb..b537cdfdf 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -74,8 +74,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (Fma.IsSupported) { float* bufferStart = this.bufferPtr; - float* bufferEnd = bufferStart + (this.Length & ~1); - Vector256 result256 = Vector256.Zero; + float* bufferEnd = bufferStart + (this.Length & ~3); + Vector256 result256_0 = Vector256.Zero; + Vector256 result256_1 = Vector256.Zero; var mask = Vector256.Create(0, 0, 0, 0, 1, 1, 1, 1); while (bufferStart < bufferEnd) @@ -87,19 +88,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // // vmovsd xmm2, [rax] ; load *(double*)bufferStart into xmm2 as [ab, _] // vpermps ymm2, ymm1, ymm2 ; permute as a float YMM register to [a, a, a, a, b, b, b, b] - // vfmadd231ps ymm0, ymm2, [r8] ; result256 = FMA(pixels, factors) + result256 + // vfmadd231ps ymm0, ymm2, [r8] ; result256_0 = FMA(pixels, factors) + result256_0 // // For tracking the codegen issue with FMA, see: https://github.com/dotnet/runtime/issues/12212. - result256 = Fma.MultiplyAdd( + // Additionally, we're also unrolling two computations per each loop iterations to leverage the + // fact that most CPUs have two ports to schedule multiply operations for FMA instructions. + result256_0 = Fma.MultiplyAdd( Unsafe.As>(ref rowStartRef), Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), - result256); + result256_0); - bufferStart += 2; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); + result256_1 = Fma.MultiplyAdd( + Unsafe.As>(ref Unsafe.Add(ref rowStartRef, 2)), + Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)(bufferStart + 2)).AsSingle(), mask), + result256_1); + + bufferStart += 4; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 4); + } + + result256_0 = Avx.Add(result256_0, result256_1); + + if ((this.Length & 3) >= 2) + { + result256_0 = Fma.MultiplyAdd( + Unsafe.As>(ref rowStartRef), + Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), + result256_0); } - Vector128 result128 = Sse.Add(result256.GetLower(), result256.GetUpper()); + Vector128 result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper()); if ((this.Length & 1) != 0) { From e68a21de52d1de7c9eaeb234ab50ec4cf470c2fc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Jan 2021 23:31:32 +0100 Subject: [PATCH 26/28] Add missing indexing update --- .../Processing/Processors/Transforms/Resize/ResizeKernel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index b537cdfdf..c79f938d7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -115,6 +115,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Unsafe.As>(ref rowStartRef), Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), result256_0); + + bufferStart += 2; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); } Vector128 result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper()); From ed4cfaa0ae4165357db4778da198189b8bc7d003 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 20 Jan 2021 17:39:12 +0100 Subject: [PATCH 27/28] Workaround for incorrect codegen on .NET 5 See Vector256.Create issue: https://github.com/dotnet/runtime/issues/47236 --- .../Processors/Transforms/Resize/ResizeKernel.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index c79f938d7..979206ad5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; #if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; #endif @@ -77,7 +78,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float* bufferEnd = bufferStart + (this.Length & ~3); Vector256 result256_0 = Vector256.Zero; Vector256 result256_1 = Vector256.Zero; - var mask = Vector256.Create(0, 0, 0, 0, 1, 1, 1, 1); + ReadOnlySpan maskBytes = new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, + }; + Vector256 mask = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(maskBytes)); while (bufferStart < bufferEnd) { From 8c7019e41e9a9dfbba63af19859194471a08be3a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 20 Jan 2021 21:50:35 +0100 Subject: [PATCH 28/28] Update image threshold for resize tests --- .../Processing/Processors/Transforms/ResizeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index f4a94782f..58b7fd12e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms testOutputDetails: workingBufferLimitInRows, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.001f), + ImageComparer.TolerantPercentage(0.004f), provider, testOutputDetails: workingBufferLimitInRows, appendPixelTypeToFileName: false);