diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs index 65a201d946..40e2da0beb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs @@ -41,12 +41,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components for (int i = 0; i < c0.Length; i++) { - float r = rLane[i]; - float g = gLane[i]; - float b = bLane[i]; - // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) - float luma = (0.299f * r) + (0.587f * g) + (0.114f * b); + float luma = (0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i]); c0[i] = luma; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 8048f3233d..86b15e888e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index 2bbce6cb1b..f01cab76cb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -2,8 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -13,6 +17,68 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public partial class JpegEncoderTests { + [Fact] + public void Encode_PreservesIptcProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.IptcProfile = new IptcProfile(); + input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IptcProfile actual = output.Metadata.IptcProfile; + Assert.NotNull(actual); + IEnumerable values = input.Metadata.IptcProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesExifProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.ExifProfile = new ExifProfile(); + input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + ExifProfile actual = output.Metadata.ExifProfile; + Assert.NotNull(actual); + IReadOnlyList values = input.Metadata.ExifProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesIccProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IccProfile actual = output.Metadata.IccProfile; + Assert.NotNull(actual); + IccProfile values = input.Metadata.IccProfile; + Assert.Equal(values.Entries, actual.Entries); + } + [Theory] [WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)] public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index cf61e57830..2ae993b8fb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,15 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; 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; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -63,6 +59,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } }; + [Fact] + public void Quality_1_And_100_Are_Not_Identical() + { + var options = new JpegEncoder + { + Quality = 1 + }; + + var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); + + using (Image input = testFile.CreateRgba32Image()) + using (var memStream0 = new MemoryStream()) + using (var memStream1 = new MemoryStream()) + { + input.SaveAsJpeg(memStream0, options); + + options.Quality = 100; + input.SaveAsJpeg(memStream1, options); + + Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); + } + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegEncodingColor.Luminance)] [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] @@ -169,36 +188,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestJpegEncoderCore(provider, colorType, 100, comparer); } - /// - /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation - /// - private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType) - { - float tolerance = 0.015f; // ~1.5% - - if (quality < 50) - { - tolerance *= 4.5f; - } - else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420) - { - tolerance *= 2.0f; - if (colorType == JpegEncodingColor.YCbCrRatio420) - { - tolerance *= 2.0f; - } - } - - return ImageComparer.Tolerant(tolerance); - } - - private static void TestJpegEncoderCore( - TestImageProvider provider, - JpegEncodingColor colorType = JpegEncodingColor.YCbCrRatio420, - int quality = 100, - ImageComparer comparer = null) + [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! @@ -211,33 +215,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; string info = $"{colorType}-Q{quality}"; - comparer ??= GetComparer(quality, colorType); + ImageComparer comparer = GetComparer(quality, colorType); // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); - } - - [Fact] - public void Quality_1_And_100_Are_Not_Identical() - { - var options = new JpegEncoder - { - Quality = 1 - }; - - var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); - - using (Image input = testFile.CreateRgba32Image()) - using (var memStream0 = new MemoryStream()) - using (var memStream1 = new MemoryStream()) - { - input.SaveAsJpeg(memStream0, options); - - options.Quality = 100; - input.SaveAsJpeg(memStream1, options); - - Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); - } + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); } [Theory] @@ -263,68 +244,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } - [Fact] - public void Encode_PreservesIptcProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.IptcProfile = new IptcProfile(); - input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - IptcProfile actual = output.Metadata.IptcProfile; - Assert.NotNull(actual); - IEnumerable values = input.Metadata.IptcProfile.Values; - Assert.Equal(values, actual.Values); - } - - [Fact] - public void Encode_PreservesExifProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.ExifProfile = new ExifProfile(); - input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - ExifProfile actual = output.Metadata.ExifProfile; - Assert.NotNull(actual); - IReadOnlyList values = input.Metadata.ExifProfile.Values; - Assert.Equal(values, actual.Values); - } - - [Fact] - public void Encode_PreservesIccProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - IccProfile actual = output.Metadata.IccProfile; - Assert.NotNull(actual); - IccProfile values = input.Metadata.IccProfile; - Assert.Equal(values.Entries, actual.Entries); - } - [Theory] [InlineData(JpegEncodingColor.YCbCrRatio420)] [InlineData(JpegEncodingColor.YCbCrRatio444)] @@ -354,5 +273,53 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg await image.SaveAsync(pausedStream, encoder, cts.Token); }); } + + /// + /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation + /// + private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType) + { + float tolerance = 0.015f; // ~1.5% + + if (quality < 50) + { + tolerance *= 4.5f; + } + else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420) + { + tolerance *= 2.0f; + if (colorType == JpegEncodingColor.YCbCrRatio420) + { + tolerance *= 2.0f; + } + } + + return ImageComparer.Tolerant(tolerance); + } + + private static void TestJpegEncoderCore( + TestImageProvider provider, + JpegEncodingColor colorType = JpegEncodingColor.YCbCrRatio420, + int quality = 100, + ImageComparer comparer = null) + 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, + ColorType = colorType + }; + 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/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index c9b0b3c55e..0a4cd34d2c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -129,13 +129,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( - provider, - pngColorType, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - appendPixelType: true, - appendPngColorType: true); + provider, + pngColorType, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + appendPixelType: true, + appendPngColorType: true); } } @@ -204,14 +204,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( - provider, - pngColorType, - (PngFilterMethod)filterMethod[0], - pngBitDepth, - interlaceMode, - appendPngColorType: true, - appendPixelType: true, - appendPngBitDepth: true); + provider, + pngColorType, + (PngFilterMethod)filterMethod[0], + pngBitDepth, + interlaceMode, + appendPngColorType: true, + appendPixelType: true, + appendPngBitDepth: true); } } } @@ -314,13 +314,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( - provider, - PngColorType.Palette, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - paletteSize: paletteSize, - appendPaletteSize: true); + provider, + PngColorType.Palette, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + paletteSize: paletteSize, + appendPaletteSize: true); } } @@ -615,7 +615,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); - // Compare to the Magick reference decoder. IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); // We compare using both our decoder and the reference decoder as pixel transformation diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg new file mode 100644 index 0000000000..5d9f955e6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acf150308d03dcf9e538eca4f5b1a4895e217c8e7fe7a3bbb7f94a32bc54c794 +size 1600914 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg new file mode 100644 index 0000000000..610b91655d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe9bcfac7e958c5195000a5c935766690c87a161e2ad87b765c0ba5c4d7a6ce8 +size 425003 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg new file mode 100644 index 0000000000..e06a83144b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fc1c157e3170aa13d39a7c8429f70c32c200d84122295b346e5ca7b4808e172 +size 757505 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg new file mode 100644 index 0000000000..9d9c930d50 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8be680ee7a03074c8747dbfffa9175d19120d43ada9406b99110370dd3cda228 +size 491941 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg new file mode 100644 index 0000000000..dde66b6f59 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b594036290443faedb7ec11ec57ab4727d3ddc68fd09b7ac5a0a121691b499a4 +size 542610 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg new file mode 100644 index 0000000000..c2389a98ef --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9df47aa652d31050034e301df65ec327aee9d2ee2ea051a3ef7f4f5915641681 +size 561591 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg new file mode 100644 index 0000000000..6e6b9f4437 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06e0b522c1e8bedf5d03f7a0bec00ef474a886def8bbd514c6f4eba6af97880d +size 652375 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg new file mode 100644 index 0000000000..767bd0c50b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9bc290239d1739febabeda84191c6fadb206638c7f1cc13240681a0c6596ecb +size 810044