diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 2b98746546..5c6535bd37 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public void AllocateComponents() { - bool fullScan = this.Progressive || this.Interleaved; + bool fullScan = this.Progressive || !this.Interleaved; for (int i = 0; i < this.ComponentCount; i++) { IJpegComponent component = this.Components[i]; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 5bad2a5b04..7fe6a4990d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -5,7 +5,6 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 4a0a728ad9..5b5dedaab3 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,18 +2,10 @@ // Licensed under the Six Labors Split License. using System; -using System.Diagnostics; -using System.IO; using System.Reflection; using System.Threading; -using PhotoSauce.MagicScaler; -using PhotoSauce.MagicScaler.Interpolators; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Memory.Internals; +using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; using Xunit.Abstractions; @@ -33,173 +25,31 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); } - const string pathTemplate = "C:\\Users\\pl4nu\\Downloads\\{0}.jpg"; - + /// + /// The main entry point. Useful for executing benchmarks and performance unit tests manually, + /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. + /// + /// + /// The arguments to pass to the program. + /// public static void Main(string[] args) { - //string imageName = "Calliphora_aligned_size"; - //string imageName = "Calliphora"; - string imageName = "1x1"; - //string imageName = "bw_check"; - //string imageName = "bw_check_color"; - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio444, 100); - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio422, 100); - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio420, 100); - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio411, 100); - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio410, 100); - //ReEncodeImage(imageName, JpegEncodingColor.Luminance, 100); - //ReEncodeImage(imageName, JpegEncodingColor.Rgb, 100); - //ReEncodeImage(imageName, JpegEncodingColor.Cmyk, 100); - - // Encoding q=75 | color=YCbCrRatio444 - // Elapsed: 4901ms across 500 iterations - // Average: 9,802ms - //BenchmarkEncoder(imageName, 500, 75, JpegEncodingColor.YCbCrRatio444); - } - - private static void BenchmarkEncoder(string fileName, int iterations, int quality, JpegEncodingColor color) - { - string loadPath = String.Format(pathTemplate, fileName); - - using var inputStream = new FileStream(loadPath, FileMode.Open); - using var saveStream = new MemoryStream(); - - var decoder = new JpegDecoder { IgnoreMetadata = true }; - using Image img = decoder.Decode(Configuration.Default, inputStream, CancellationToken.None); - - var encoder = new JpegEncoder() - { - Quality = quality, - ColorType = color, - }; - - Stopwatch sw = new Stopwatch(); - sw.Start(); - for (int i = 0; i < iterations; i++) - { - img.SaveAsJpeg(saveStream, encoder); - saveStream.Position = 0; - } - sw.Stop(); - - Console.WriteLine($"// Encoding q={quality} | color={color}\n" + - $"// Elapsed: {sw.ElapsedMilliseconds}ms across {iterations} iterations\n" + - $"// Average: {(double)sw.ElapsedMilliseconds / iterations}ms"); - } - - private static void BenchmarkDecoder(string fileName, int iterations) - { - string loadPath = String.Format(pathTemplate, fileName); - - using var fileStream = new FileStream(loadPath, FileMode.Open); - using var inputStream = new MemoryStream(); - fileStream.CopyTo(inputStream); - - var decoder = new JpegDecoder { IgnoreMetadata = true }; - - var sw = new Stopwatch(); - sw.Start(); - for (int i = 0; i < iterations; i++) + try { - inputStream.Position = 0; - using Image img = decoder.Decode(Configuration.Default, inputStream, CancellationToken.None); + LoadResizeSaveParallelMemoryStress.Run(args); } - sw.Stop(); - - Console.WriteLine($"// Decoding\n" + - $"// Elapsed: {sw.ElapsedMilliseconds}ms across {iterations} iterations\n" + - $"// Average: {(double)sw.ElapsedMilliseconds / iterations}ms"); - } - - private static void BenchmarkResizingLoop__explicit(string fileName, Size targetSize, int iterations) - { - string loadPath = String.Format(pathTemplate, fileName); - - using var fileStream = new FileStream(loadPath, FileMode.Open); - using var saveStream = new MemoryStream(); - using var inputStream = new MemoryStream(); - fileStream.CopyTo(inputStream); - - var decoder = new JpegDecoder { IgnoreMetadata = true }; - var encoder = new JpegEncoder { ColorType = JpegEncodingColor.YCbCrRatio444 }; - - var sw = new Stopwatch(); - sw.Start(); - for (int i = 0; i < iterations; i++) + catch (Exception ex) { - inputStream.Position = 0; - using Image img = decoder.Decode(Configuration.Default, inputStream, CancellationToken.None); - img.Mutate(ctx => ctx.Resize(targetSize, KnownResamplers.Box, false)); - img.SaveAsJpeg(saveStream, encoder); + Console.WriteLine(ex); } - sw.Stop(); - - Console.WriteLine($"// Decode-Resize-Encode w/ Mutate()\n" + - $"// Elapsed: {sw.ElapsedMilliseconds}ms across {iterations} iterations\n" + - $"// Average: {(double)sw.ElapsedMilliseconds / iterations}ms"); - } - private static void ReEncodeImage(string fileName, JpegEncodingColor mode, int? quality = null) - { - string loadPath = String.Format(pathTemplate, fileName); - using Image img = Image.Load(loadPath); - - string savePath = String.Format(pathTemplate, $"q{quality}_{mode}_test_{fileName}"); - var encoder = new JpegEncoder() - { - Quality = quality, - ColorType = mode, - }; - img.SaveAsJpeg(savePath, encoder); - } - - private static void ReencodeImageResize__explicit(string fileName, Size targetSize, IResampler sampler, int? quality = null) - { - string loadPath = String.Format(pathTemplate, fileName); - string savePath = String.Format(pathTemplate, $"is_res_{sampler.GetType().Name}[{targetSize.Width}x{targetSize.Height}]_{fileName}"); - - var decoder = new JpegDecoder(); - var encoder = new JpegEncoder() - { - Quality = quality, - ColorType = JpegEncodingColor.YCbCrRatio444 - }; - - using Image img = decoder.Decode(Configuration.Default, File.OpenRead(loadPath), CancellationToken.None); - img.Mutate(ctx => ctx.Resize(targetSize, sampler, compand: false)); - img.SaveAsJpeg(savePath, encoder); - } - - private static void ReencodeImageResize__Netvips(string fileName, Size targetSize, int? quality) - { - string loadPath = String.Format(pathTemplate, fileName); - string savePath = String.Format(pathTemplate, $"netvips_resize_{fileName}"); - - using var thumb = NetVips.Image.Thumbnail(loadPath, targetSize.Width, targetSize.Height); - - // Save the results - thumb.Jpegsave(savePath, q: quality, strip: true, subsampleMode: NetVips.Enums.ForeignSubsample.Off); - } - - private static void ReencodeImageResize__MagicScaler(string fileName, Size targetSize, int quality) - { - string loadPath = String.Format(pathTemplate, fileName); - string savePath = String.Format(pathTemplate, $"magicscaler_resize_{fileName}"); + // RunJpegEncoderProfilingTests(); + // RunJpegColorProfilingTests(); + // RunDecodeJpegProfilingTests(); + // RunToVector4ProfilingTest(); + // RunResizeProfilingTest(); - var settings = new ProcessImageSettings() - { - Width = targetSize.Width, - Height = targetSize.Height, - SaveFormat = FileFormat.Jpeg, - JpegQuality = quality, - JpegSubsampleMode = ChromaSubsampleMode.Subsample444, - Sharpen = false, - ColorProfileMode = ColorProfileMode.Ignore, - HybridMode = HybridScaleMode.Turbo, - }; - - using var output = new FileStream(savePath, FileMode.Create); - MagicImageProcessor.ProcessImage(loadPath, output, settings); + // Console.ReadLine(); } private static Version GetNetCoreVersion() @@ -216,12 +66,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox return null; } - private static void RunJpegEncoderProfilingTests() - { - var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); - benchmarks.EncodeJpeg_SingleMidSize(); - } - private static void RunResizeProfilingTest() { var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); @@ -233,19 +77,5 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox var tests = new PixelOperationsTests.Rgba32_OperationsTests(new ConsoleOutput()); tests.Benchmark_ToVector4(); } - - private static void RunDecodeJpegProfilingTests() - { - Console.WriteLine("RunDecodeJpegProfilingTests..."); - var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); - foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData) - { - string fileName = (string)data[0]; - int executionCount = (int)data[1]; - benchmarks.DecodeJpeg(fileName, executionCount); - } - - Console.WriteLine("DONE."); - } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index af07338fb5..60f45664d3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; @@ -17,6 +18,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public partial class JpegEncoderTests { + public static readonly TheoryData RatioFiles = + new() + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; + + public static readonly TheoryData QualityFiles = + new() + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; + [Fact] public void Encode_PreservesIptcProfile() { @@ -95,5 +111,71 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Null(ex); } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } + + [Theory] + [MemberData(nameof(QualityFiles))] + public void Encode_PreservesQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + JpegMetadata meta = output.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); + } + } + } + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] + public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) + where TPixel : unmanaged, IPixel + { + // arrange + using Image input = provider.GetImage(JpegDecoder); + using var memoryStream = new MemoryStream(); + + // act + input.Save(memoryStream, JpegEncoder); + + // assert + memoryStream.Position = 0; + using var output = Image.Load(memoryStream); + JpegMetadata meta = output.Metadata.GetJpegMetadata(); + Assert.Equal(expectedColorType, meta.ColorType); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 4f16282f47..8a78ef6485 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -5,7 +5,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -22,139 +21,122 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static JpegDecoder JpegDecoder => new(); - public static readonly TheoryData QualityFiles = - new() - { - { TestImages.Jpeg.Baseline.Calliphora, 80 }, - { TestImages.Jpeg.Progressive.Fb, 75 } - }; - - public static readonly TheoryData BitsPerPixel_Quality = - new() - { - { JpegEncodingColor.YCbCrRatio420, 40 }, - { JpegEncodingColor.YCbCrRatio420, 60 }, - { JpegEncodingColor.YCbCrRatio420, 100 }, - { JpegEncodingColor.YCbCrRatio444, 40 }, - { JpegEncodingColor.YCbCrRatio444, 60 }, - { JpegEncodingColor.YCbCrRatio444, 100 }, - { JpegEncodingColor.Rgb, 40 }, - { JpegEncodingColor.Rgb, 60 }, - { JpegEncodingColor.Rgb, 100 } - }; + private static readonly TheoryData TestQualities = new() + { + 40, + 80, + 100, + }; - public static readonly TheoryData Grayscale_Quality = - new() - { - { 40 }, - { 60 }, - { 100 } - }; + public static readonly TheoryData NonSubsampledEncodingSetups = new() + { + { JpegEncodingColor.Rgb, 100, 0.0238f / 100 }, + { JpegEncodingColor.Rgb, 80, 1.3044f / 100 }, + { JpegEncodingColor.Rgb, 40, 2.9879f / 100 }, - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, - { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } - }; + { JpegEncodingColor.YCbCrRatio444, 100, 0.0780f / 100 }, + { JpegEncodingColor.YCbCrRatio444, 80, 1.4585f / 100 }, + { JpegEncodingColor.YCbCrRatio444, 40, 3.1413f / 100 }, + }; - [Fact] - public void Quality_1_And_100_Are_Not_Identical() + public static readonly TheoryData SubsampledEncodingSetups = new() { - var options = new JpegEncoder - { - Quality = 1 - }; + { JpegEncodingColor.YCbCrRatio422, 100, 0.4895f / 100 }, + { JpegEncodingColor.YCbCrRatio422, 80, 1.6043f / 100 }, + { JpegEncodingColor.YCbCrRatio422, 40, 3.1996f / 100 }, - var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); + { JpegEncodingColor.YCbCrRatio420, 100, 0.5790f / 100 }, + { JpegEncodingColor.YCbCrRatio420, 80, 1.6692f / 100 }, + { JpegEncodingColor.YCbCrRatio420, 40, 3.2324f / 100 }, - using (Image input = testFile.CreateRgba32Image()) - using (var memStream0 = new MemoryStream()) - using (var memStream1 = new MemoryStream()) - { - input.SaveAsJpeg(memStream0, options); + { JpegEncodingColor.YCbCrRatio411, 100, 0.6868f / 100 }, + { JpegEncodingColor.YCbCrRatio411, 80, 1.7139f / 100 }, + { JpegEncodingColor.YCbCrRatio411, 40, 3.2634f / 100 }, - options.Quality = 100; - input.SaveAsJpeg(memStream1, options); + { JpegEncodingColor.YCbCrRatio410, 100, 0.7357f / 100 }, + { JpegEncodingColor.YCbCrRatio410, 80, 1.7495f / 100 }, + { JpegEncodingColor.YCbCrRatio410, 40, 3.2911f / 100 }, + }; - Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); - } - } + public static readonly TheoryData CmykEncodingSetups = new() + { + { JpegEncodingColor.Cmyk, 100, 0.0159f / 100 }, + { JpegEncodingColor.Cmyk, 80, 0.3922f / 100 }, + { JpegEncodingColor.Cmyk, 40, 0.6488f / 100 }, + }; - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegEncodingColor.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegEncodingColor.Rgb)] - public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) - where TPixel : unmanaged, IPixel + public static readonly TheoryData YcckEncodingSetups = new() { - // arrange - using Image input = provider.GetImage(JpegDecoder); - using var memoryStream = new MemoryStream(); - - // act - input.Save(memoryStream, JpegEncoder); - - // assert - memoryStream.Position = 0; - using var output = Image.Load(memoryStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } + { JpegEncodingColor.Ycck, 100, 0.0356f / 100 }, + { JpegEncodingColor.Ycck, 80, 0.1245f / 100 }, + { JpegEncodingColor.Ycck, 40, 0.2663f / 100 }, + }; + + public static readonly TheoryData LuminanceEncodingSetups = new() + { + { JpegEncodingColor.Luminance, 100, 0.0175f / 100 }, + { JpegEncodingColor.Luminance, 80, 0.6730f / 100 }, + { JpegEncodingColor.Luminance, 40, 0.9941f / 100 }, + }; + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeBaseline_Interleaved(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, tolerance); [Theory] - [MemberData(nameof(QualityFiles))] - public void Encode_PreservesQuality(string imagePath, int quality) + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeBaseline_NonInterleavedMode(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel { - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + using Image image = provider.GetImage(); + + var encoder = new JpegEncoder { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } - } - } - } + Quality = quality, + ColorType = colorType, + Interleaved = false, + }; + string info = $"{colorType}-Q{quality}"; - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] - public void EncodeBaseline_CalliphoraPartial(TestImageProvider provider, JpegEncodingColor colorType, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); + ImageComparer comparer = new TolerantImageComparer(tolerance); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); + } [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 158, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)] - public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality) + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 600, 400, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 158, 24, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 153, 21, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 138, 24, PixelTypes.Rgb24)] + public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] - [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] - [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 143, 81, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] - public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.15f)); + [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] + [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 255, 100, 50, 255, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 7, 5, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 48, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 73, 71, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 24, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 46, 8, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 51, 7, PixelTypes.Rgb24)] + public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, ImageComparer.Tolerant(0.12f)); [Theory] - [WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgba32, 100)] + [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgb24, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L8, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L16, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)] @@ -163,20 +145,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegEncodingColor.Luminance, quality); [Theory] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 96, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality) + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 96, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality) + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f)); [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] - [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] - [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegEncodingColor.YCbCrRatio420)] - [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(677, 683, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithSolidFilledImages(400, 400, nameof(Color.Red), PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegEncodingColor colorType) where TPixel : unmanaged, IPixel { @@ -188,96 +171,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestJpegEncoderCore(provider, colorType, 100, comparer); } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.L8, JpegEncodingColor.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio422)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio411)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio410)] - public void EncodeBaseline_WorksWithAllColorTypes(TestImageProvider provider, JpegEncodingColor colorType) - where TPixel : unmanaged, IPixel - { - // all reference output images are saved with quality=100 - const int quality = 100; - - using Image image = provider.GetImage(); - - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); - - var encoder = new JpegEncoder - { - Quality = quality, - ColorType = colorType - }; - string info = $"{colorType}-Q{quality}"; - - ImageComparer comparer = GetComparer(quality, colorType); - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.L8, JpegEncodingColor.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Ycck)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio422)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio411)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio410)] - public void EncodeBaseline_WorksInNonInterleavedMode(TestImageProvider provider, JpegEncodingColor colorType) - where TPixel : unmanaged, IPixel - { - // all reference output images are saved with quality=100 - const int quality = 100; - - using Image image = provider.GetImage(); - - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); - - var encoder = new JpegEncoder - { - ColorType = colorType, - Interleaved = false - }; - string info = $"{colorType}-Q{quality}"; - - ImageComparer comparer = GetComparer(quality, colorType); - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) - { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } - } - } - [Theory] [InlineData(JpegEncodingColor.YCbCrRatio420)] [InlineData(JpegEncodingColor.YCbCrRatio444)] @@ -331,18 +224,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return ImageComparer.Tolerant(tolerance); } - private static void TestJpegEncoderCore( - TestImageProvider provider, - JpegEncodingColor colorType = JpegEncodingColor.YCbCrRatio420, - int quality = 100, - ImageComparer comparer = null) + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality) + where TPixel : unmanaged, IPixel + => TestJpegEncoderCore(provider, colorType, quality, GetComparer(quality, colorType)); + + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel + => TestJpegEncoderCore(provider, colorType, quality, new TolerantImageComparer(tolerance)); + + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, ImageComparer comparer) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); - var encoder = new JpegEncoder { Quality = quality, @@ -350,8 +244,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; string info = $"{colorType}-Q{quality}"; - comparer ??= GetComparer(quality, colorType); - // Does DebugSave & load reference CompareToReferenceInput(): image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs deleted file mode 100644 index 08b7714ec0..0000000000 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.IO; -using System.Linq; -using System.Numerics; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; - -using Xunit; -using Xunit.Abstractions; - -// in this file, comments are used for disabling stuff for local execution -#pragma warning disable SA1515 - -namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks -{ - public class JpegProfilingBenchmarks : MeasureFixture - { - public JpegProfilingBenchmarks(ITestOutputHelper output) - : base(output) - { - } - - public static readonly TheoryData DecodeJpegData = new TheoryData - { - { TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, 20 }, - { TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, 20 }, - { TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, 40 }, - // { TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, 10 }, - // { TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, 5 }, - { TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, 5 } - }; - - [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [MemberData(nameof(DecodeJpegData))] - public void DecodeJpeg(string fileName, int executionCount) - { - var decoder = new JpegDecoder() - { - IgnoreMetadata = true - }; - this.DecodeJpegBenchmarkImpl(fileName, decoder, executionCount); - } - - private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder, int executionCount) - { - // do not run this on CI even by accident - if (TestEnvironment.RunsOnCI) - { - return; - } - - if (!Vector.IsHardwareAccelerated) - { - throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); - } - - string path = TestFile.GetInputFileFullPath(fileName); - byte[] bytes = File.ReadAllBytes(path); - - this.Measure( - executionCount, - () => - { - var img = Image.Load(bytes, decoder); - img.Dispose(); - }, -#pragma warning disable SA1515 // Single-line comment should be preceded by blank line - // ReSharper disable once ExplicitCallerInfoArgument - $"Decode {fileName}"); -#pragma warning restore SA1515 // Single-line comment should be preceded by blank line - } - - [Fact(Skip = ProfilingSetup.SkipProfilingTests)] - public void EncodeJpeg_SingleMidSize() - { - string path = TestFile.GetInputFileFullPath(TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr); - using var image = Image.Load(path); - image.Metadata.ExifProfile = null; - - using var ms = new MemoryStream(); - for (int i = 0; i < 30; i++) - { - image.SaveAsJpeg(ms); - ms.Seek(0, SeekOrigin.Begin); - } - } - - // Benchmark, enable manually! - [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [InlineData(1, 75, JpegEncodingColor.YCbCrRatio420)] - [InlineData(30, 75, JpegEncodingColor.YCbCrRatio420)] - [InlineData(30, 75, JpegEncodingColor.YCbCrRatio444)] - [InlineData(30, 100, JpegEncodingColor.YCbCrRatio444)] - public void EncodeJpeg(int executionCount, int quality, JpegEncodingColor colorType) - { - // do not run this on CI even by accident - if (TestEnvironment.RunsOnCI) - { - return; - } - - string[] testFiles = TestImages.Bmp.Benchmark - .Concat(new[] { TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk }).ToArray(); - - Image[] testImages = testFiles.Select( - tf => TestImageProvider.File(tf, pixelTypeOverride: PixelTypes.Rgba32).GetImage()).ToArray(); - - using (var ms = new MemoryStream()) - { - this.Measure( - executionCount, - () => - { - foreach (Image img in testImages) - { - var options = new JpegEncoder { Quality = quality, ColorType = colorType }; - img.Save(ms, options); - ms.Seek(0, SeekOrigin.Begin); - } - }, -#pragma warning disable SA1515 // Single-line comment should be preceded by blank line - // ReSharper disable once ExplicitCallerInfoArgument - $@"Encode {testFiles.Length} images"); -#pragma warning restore SA1515 // Single-line comment should be preceded by blank line - } - - foreach (Image image in testImages) - { - image.Dispose(); - } - } - } -}