From bb9ed6533370ed83ee7adc7d507d5b1f3929fa89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Mutnia=C5=84ski?= Date: Sun, 7 Jan 2024 15:49:34 +0100 Subject: [PATCH 1/9] add jpeg com marker support --- ImageSharp.sln | 1 + .../Formats/Jpeg/JpegDecoderCore.cs | 20 ++++++++- .../Formats/Jpeg/JpegEncoderCore.cs | 45 +++++++++++++++++++ src/ImageSharp/Formats/Jpeg/JpegMetadata.cs | 7 +++ .../Formats/Jpeg/MetadataExtensions.cs | 21 +++++++++ .../Formats/Jpg/JpegDecoderTests.cs | 15 +++++++ .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 44 ++++++++++++++++++ .../Formats/Jpg/JpegMetadataTests.cs | 24 +++++++++- tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Jpg/issues/issue-2067-comment.jpg | 3 ++ 10 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 tests/Images/Input/Jpg/issues/issue-2067-comment.jpg diff --git a/ImageSharp.sln b/ImageSharp.sln index 2967acb8ff..82eeefcde6 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -237,6 +237,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68 tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg = tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg = tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg = tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg + tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fuzz", "fuzz", "{516A3532-6AC2-417B-AD79-9BD5D0D378A0}" diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index ccace190f9..e11923ac8c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; @@ -481,7 +482,7 @@ internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralC case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: - stream.Skip(markerContentByteSize); + this.ProcessComMarker(stream, markerContentByteSize); break; case JpegConstants.Markers.DAC: @@ -515,6 +516,23 @@ public void Dispose() this.scanDecoder = null; } + /// + /// Assigns COM marker bytes to comment property + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessComMarker(BufferedReadStream stream, int markerContentByteSize) + { + Span temp = stackalloc byte[markerContentByteSize]; + char[] chars = new char[markerContentByteSize]; + JpegMetadata metadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance); + + stream.Read(temp); + Encoding.ASCII.GetChars(temp, chars); + + metadata.Comments.Add(chars); + } + /// /// Returns encoded colorspace based on the adobe APP14 marker. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 7fc2a1f45e..cc6042b6ef 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -3,6 +3,7 @@ #nullable disable using System.Buffers.Binary; +using System.Text; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; @@ -89,6 +90,9 @@ public void Encode(Image image, Stream stream, CancellationToken // Write Exif, XMP, ICC and IPTC profiles this.WriteProfiles(metadata, buffer); + // Write comments + this.WriteComment(jpegMetadata); + // Write the image dimensions. this.WriteStartOfFrame(image.Width, image.Height, frameConfig, buffer); @@ -167,6 +171,47 @@ private void WriteJfifApplicationHeader(ImageMetadata meta, Span buffer) this.outputStream.Write(buffer, 0, 18); } + /// + /// Writes comment + /// + /// The image metadata. + private void WriteComment(JpegMetadata metadata) + { + if (metadata.Comments is { Count: 0 }) + { + return; + } + + // Length (comment strings lengths) + (comments markers with payload sizes) + int commentsBytes = metadata.Comments.Sum(x => x.Length) + (metadata.Comments.Count * 4); + int commentStart = 0; + Span commentBuffer = stackalloc byte[commentsBytes]; + + foreach (Memory comment in metadata.Comments) + { + int totalComLength = comment.Length + 4; + + Span commentData = commentBuffer.Slice(commentStart, totalComLength); + Span markers = commentData.Slice(0, 2); + Span payloadSize = commentData.Slice(2, 2); + Span payload = commentData.Slice(4, comment.Length); + + // Beginning of comment ff fe + markers[0] = JpegConstants.Markers.XFF; + markers[1] = JpegConstants.Markers.COM; + + // Write payload size + BinaryPrimitives.WriteInt16BigEndian(payloadSize, (short)(commentData.Length - 2)); + + Encoding.ASCII.GetBytes(comment.Span, payload); + + // Indicate begin of next comment in buffer + commentStart += totalComLength; + } + + this.outputStream.Write(commentBuffer, 0, commentBuffer.Length); + } + /// /// Writes the Define Huffman Table marker and tables. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index 59fc2f9cba..61fe3b214e 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -15,6 +15,7 @@ public class JpegMetadata : IDeepCloneable /// public JpegMetadata() { + this.Comments = new List>(); } /// @@ -25,6 +26,7 @@ private JpegMetadata(JpegMetadata other) { this.ColorType = other.ColorType; + this.Comments = other.Comments; this.LuminanceQuality = other.LuminanceQuality; this.ChrominanceQuality = other.ChrominanceQuality; } @@ -101,6 +103,11 @@ public int Quality /// public bool? Progressive { get; internal set; } + /// + /// Gets the comments. + /// + public ICollection>? Comments { get; } + /// public IDeepCloneable DeepClone() => new JpegMetadata(this); } diff --git a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs index 753dfdb60e..53efc7d0ca 100644 --- a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Text; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata; @@ -17,4 +18,24 @@ public static partial class MetadataExtensions /// The metadata this method extends. /// The . public static JpegMetadata GetJpegMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(JpegFormat.Instance); + + /// + /// Saves the comment into + /// + /// The metadata this method extends. + /// The comment string. + public static void SaveComment(this JpegMetadata metadata, string comment) + { + ASCIIEncoding encoding = new(); + + byte[] bytes = encoding.GetBytes(comment); + metadata.Comments?.Add(encoding.GetChars(bytes)); + } + + /// + /// Gets the comments from + /// + /// The metadata this method extends. + /// The IEnumerable string of comments. + public static IEnumerable? GetComments(this JpegMetadata metadata) => metadata.Comments?.Select(x => x.ToString()); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index c8d93f6e9e..b219e715f6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -364,4 +364,19 @@ public void Issue2517_DecodeWorks(TestImageProvider provider) image.DebugSave(provider); image.CompareToOriginal(provider); } + + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2067_CommentMarker, PixelTypes.Rgba32)] + public void JpegDecoder_DecodeMetadataComment(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + string expectedComment = "TEST COMMENT"; + using Image image = provider.GetImage(JpegDecoder.Instance); + JpegMetadata metadata = image.Metadata.GetJpegMetadata(); + + Assert.Equal(1, metadata.Comments?.Count); + Assert.Equal(expectedComment, metadata.GetComments()?.FirstOrDefault()); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index 2b721b9b51..50f47a1345 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -154,6 +154,50 @@ public void Encode_PreservesQuality(string imagePath, int quality) } } + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2067_CommentMarker, PixelTypes.Rgba32)] + public void Encode_PreservesComments(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using var input = provider.GetImage(JpegDecoder.Instance); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + JpegMetadata actual = output.Metadata.GetJpegMetadata(); + Assert.NotEmpty(actual.Comments); + Assert.Equal(1, actual.Comments.Count); + Assert.Equal("TEST COMMENT", actual.Comments.ElementAt(0).ToString()); + } + + [Fact] + public void Encode_SavesMultipleComments() + { + // arrange + using var input = new Image(1, 1); + JpegMetadata meta = input.Metadata.GetJpegMetadata(); + using var memStream = new MemoryStream(); + + // act + meta.SaveComment("First comment"); + meta.SaveComment("Second Comment"); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + JpegMetadata actual = output.Metadata.GetJpegMetadata(); + Assert.NotEmpty(actual.Comments); + Assert.Equal(2, actual.Comments.Count); + Assert.Equal(meta.Comments.ElementAt(0).ToString(), actual.Comments.ElementAt(0).ToString()); + Assert.Equal(meta.Comments.ElementAt(1).ToString(), actual.Comments.ElementAt(1).ToString()); + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index 05f22667dc..901bb4619a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Collections.ObjectModel; using SixLabors.ImageSharp.Formats.Jpeg; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -55,6 +56,27 @@ public void Quality_ReturnsMaxQuality() var meta = new JpegMetadata { LuminanceQuality = qualityLuma, ChrominanceQuality = qualityChroma }; - Assert.Equal(meta.Quality, qualityLuma); + Assert.Equal(meta.Quality, qualityLuma); + } + + [Fact] + public void Comment_EmptyComment() + { + var meta = new JpegMetadata(); + + Assert.True(Array.Empty>().SequenceEqual(meta.Comments)); + } + + [Fact] + public void Comment_OnlyComment() + { + string comment = "test comment"; + var expectedCollection = new Collection> { new(comment.ToCharArray()) }; + + var meta = new JpegMetadata(); + meta.Comments?.Add(comment.ToCharArray()); + + Assert.Equal(1, meta.Comments?.Count); + Assert.True(expectedCollection.FirstOrDefault().ToString() == meta.Comments?.FirstOrDefault().ToString()); } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8aa95d3496..0dab4ff87b 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -309,6 +309,7 @@ public static class Issues public const string Issue2564 = "Jpg/issues/issue-2564.jpg"; public const string HangBadScan = "Jpg/issues/Hang_C438A851.jpg"; public const string Issue2517 = "Jpg/issues/issue2517-bad-d7.jpg"; + public const string Issue2067_CommentMarker = "Jpg/issues/issue-2067-comment.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/issue-2067-comment.jpg b/tests/Images/Input/Jpg/issues/issue-2067-comment.jpg new file mode 100644 index 0000000000..18dc6f2e32 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue-2067-comment.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d87b5429adeffcfac535aa8af2ec9801bf6c965a2e6751cfec4f8534195ba8f4 +size 21082 From b3a8452edc95ed4603b13fc78dab90394b5a2891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Mutnia=C5=84ski?= Date: Sun, 7 Jan 2024 18:53:03 +0100 Subject: [PATCH 2/9] Rename SaveComment to SetComment, add index, add clearcomments --- .../Formats/Jpeg/MetadataExtensions.cs | 23 +++++++++++++++---- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 15 ++++++++++++ .../Formats/Jpg/JpegDecoderTests.cs | 15 ------------ .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 10 ++++---- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs index 53efc7d0ca..0c66fcbdd7 100644 --- a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs @@ -20,22 +20,35 @@ public static partial class MetadataExtensions public static JpegMetadata GetJpegMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(JpegFormat.Instance); /// - /// Saves the comment into + /// Sets the comment in /// /// The metadata this method extends. + /// The index of comment to be inserted to. /// The comment string. - public static void SaveComment(this JpegMetadata metadata, string comment) + public static void SetComment(this JpegMetadata metadata, int index, string comment) { - ASCIIEncoding encoding = new(); + if (metadata.Comments == null) + { + return; + } + ASCIIEncoding encoding = new(); byte[] bytes = encoding.GetBytes(comment); - metadata.Comments?.Add(encoding.GetChars(bytes)); + List>? comments = metadata.Comments as List>; + comments?.Insert(index, encoding.GetChars(bytes)); } /// /// Gets the comments from /// /// The metadata this method extends. + /// The index of comment. /// The IEnumerable string of comments. - public static IEnumerable? GetComments(this JpegMetadata metadata) => metadata.Comments?.Select(x => x.ToString()); + public static string? GetComment(this JpegMetadata metadata, int index) => metadata.Comments?.ElementAtOrDefault(index).ToString(); + + /// + /// Clears comments + /// + /// The . + public static void ClearComments(this JpegMetadata metadata) => metadata.Comments?.Clear(); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 1c203e7342..fb37a956d0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -425,6 +425,21 @@ public void EncodedStringTags_Read() VerifyEncodedStrings(exif); } + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2067_CommentMarker, PixelTypes.Rgba32)] + public void JpegDecoder_DecodeMetadataComment(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + string expectedComment = "TEST COMMENT"; + using Image image = provider.GetImage(JpegDecoder.Instance); + JpegMetadata metadata = image.Metadata.GetJpegMetadata(); + + Assert.Equal(1, metadata.Comments?.Count); + Assert.Equal(expectedComment, metadata.GetComment(0)); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + private static void VerifyEncodedStrings(ExifProfile exif) { Assert.NotNull(exif); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index b219e715f6..c8d93f6e9e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -364,19 +364,4 @@ public void Issue2517_DecodeWorks(TestImageProvider provider) image.DebugSave(provider); image.CompareToOriginal(provider); } - - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2067_CommentMarker, PixelTypes.Rgba32)] - public void JpegDecoder_DecodeMetadataComment(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string expectedComment = "TEST COMMENT"; - using Image image = provider.GetImage(JpegDecoder.Instance); - JpegMetadata metadata = image.Metadata.GetJpegMetadata(); - - Assert.Equal(1, metadata.Comments?.Count); - Assert.Equal(expectedComment, metadata.GetComments()?.FirstOrDefault()); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index 50f47a1345..fa54859a36 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -184,8 +184,8 @@ public void Encode_SavesMultipleComments() using var memStream = new MemoryStream(); // act - meta.SaveComment("First comment"); - meta.SaveComment("Second Comment"); + meta.SetComment(0, "First comment"); + meta.SetComment(1, "Second Comment"); input.Save(memStream, JpegEncoder); // assert @@ -193,9 +193,9 @@ public void Encode_SavesMultipleComments() using var output = Image.Load(memStream); JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); - Assert.Equal(2, actual.Comments.Count); - Assert.Equal(meta.Comments.ElementAt(0).ToString(), actual.Comments.ElementAt(0).ToString()); - Assert.Equal(meta.Comments.ElementAt(1).ToString(), actual.Comments.ElementAt(1).ToString()); + Assert.Equal(2, actual.Comments?.Count); + Assert.Equal(meta.Comments?.ElementAt(0).ToString(), actual.Comments?.ElementAt(0).ToString()); + Assert.Equal(meta.Comments?.ElementAt(1).ToString(), actual.Comments?.ElementAt(1).ToString()); } [Theory] From d6165909fd7be5e45b2f176373dd9694e1ab93e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Mutnia=C5=84ski?= Date: Sun, 7 Jan 2024 20:13:19 +0100 Subject: [PATCH 3/9] tab --- tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index 901bb4619a..8b991228b4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -56,7 +56,7 @@ public void Quality_ReturnsMaxQuality() var meta = new JpegMetadata { LuminanceQuality = qualityLuma, ChrominanceQuality = qualityChroma }; - Assert.Equal(meta.Quality, qualityLuma); + Assert.Equal(meta.Quality, qualityLuma); } [Fact] From 51272021290112a51812c53eefb9d0d4124b13ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Mutnia=C5=84ski?= Date: Tue, 23 Jan 2024 17:00:34 +0100 Subject: [PATCH 4/9] pr comment fix --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs | 2 +- .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index e11923ac8c..d6b40fa7f7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -523,7 +523,7 @@ public void Dispose() /// The remaining bytes in the segment block. private void ProcessComMarker(BufferedReadStream stream, int markerContentByteSize) { - Span temp = stackalloc byte[markerContentByteSize]; + Span temp = new byte[markerContentByteSize]; char[] chars = new char[markerContentByteSize]; JpegMetadata metadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance); diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index cc6042b6ef..1b2b4cbb1c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -185,7 +185,7 @@ private void WriteComment(JpegMetadata metadata) // Length (comment strings lengths) + (comments markers with payload sizes) int commentsBytes = metadata.Comments.Sum(x => x.Length) + (metadata.Comments.Count * 4); int commentStart = 0; - Span commentBuffer = stackalloc byte[commentsBytes]; + Span commentBuffer = new byte[commentsBytes]; foreach (Memory comment in metadata.Comments) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index fa54859a36..bd68eaf208 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -137,7 +137,7 @@ public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolut [MemberData(nameof(QualityFiles))] public void Encode_PreservesQuality(string imagePath, int quality) { - var testFile = TestFile.Create(imagePath); + TestFile testFile = TestFile.Create(imagePath); using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) @@ -160,7 +160,7 @@ public void Encode_PreservesComments(TestImageProvider provider) where TPixel : unmanaged, IPixel { // arrange - using var input = provider.GetImage(JpegDecoder.Instance); + using Image input = provider.GetImage(JpegDecoder.Instance); using var memStream = new MemoryStream(); // act @@ -168,7 +168,7 @@ public void Encode_PreservesComments(TestImageProvider provider) // assert memStream.Position = 0; - using var output = Image.Load(memStream); + using Image output = Image.Load(memStream); JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(1, actual.Comments.Count); @@ -190,7 +190,7 @@ public void Encode_SavesMultipleComments() // assert memStream.Position = 0; - using var output = Image.Load(memStream); + using Image output = Image.Load(memStream); JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(2, actual.Comments?.Count); From 9260be9d2996c614d7c992540ebef6ab4d2ca7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Mutnia=C5=84ski?= Date: Thu, 25 Jan 2024 19:19:41 +0100 Subject: [PATCH 5/9] Add com character limit, comment as IList, remove unnecessary extension methods --- .../Formats/Jpeg/JpegEncoderCore.cs | 43 +++++++++++-------- src/ImageSharp/Formats/Jpeg/JpegMetadata.cs | 2 +- .../Formats/Jpeg/MetadataExtensions.cs | 33 -------------- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 4 +- .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 12 +++--- 5 files changed, 34 insertions(+), 60 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 1b2b4cbb1c..4dc9202070 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -177,39 +177,46 @@ private void WriteJfifApplicationHeader(ImageMetadata meta, Span buffer) /// The image metadata. private void WriteComment(JpegMetadata metadata) { - if (metadata.Comments is { Count: 0 }) + int maxCommentLength = 65533; + + if (metadata.Comments.Count == 0) { return; } - // Length (comment strings lengths) + (comments markers with payload sizes) - int commentsBytes = metadata.Comments.Sum(x => x.Length) + (metadata.Comments.Count * 4); - int commentStart = 0; - Span commentBuffer = new byte[commentsBytes]; - - foreach (Memory comment in metadata.Comments) + for (int i = 0; i < metadata.Comments.Count; i++) { - int totalComLength = comment.Length + 4; + Memory chars = metadata.Comments[i]; + + if (chars.Length > maxCommentLength) + { + Memory splitComment = chars.Slice(maxCommentLength, chars.Length - maxCommentLength); + metadata.Comments.Insert(i + 1, splitComment); - Span commentData = commentBuffer.Slice(commentStart, totalComLength); - Span markers = commentData.Slice(0, 2); - Span payloadSize = commentData.Slice(2, 2); - Span payload = commentData.Slice(4, comment.Length); + // We don't want to keep the extra bytes + chars = chars.Slice(0, maxCommentLength); + } + + int commentLength = chars.Length + 4; + + Span comment = new byte[commentLength]; + Span markers = comment.Slice(0, 2); + Span payloadSize = comment.Slice(2, 2); + Span payload = comment.Slice(4, chars.Length); // Beginning of comment ff fe markers[0] = JpegConstants.Markers.XFF; markers[1] = JpegConstants.Markers.COM; // Write payload size - BinaryPrimitives.WriteInt16BigEndian(payloadSize, (short)(commentData.Length - 2)); + int comWithoutMarker = commentLength - 2; + payloadSize[0] = (byte)((comWithoutMarker >> 8) & 0xFF); + payloadSize[1] = (byte)(comWithoutMarker & 0xFF); - Encoding.ASCII.GetBytes(comment.Span, payload); + Encoding.ASCII.GetBytes(chars.Span, payload); - // Indicate begin of next comment in buffer - commentStart += totalComLength; + this.outputStream.Write(comment, 0, comment.Length); } - - this.outputStream.Write(commentBuffer, 0, commentBuffer.Length); } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index 61fe3b214e..bf758dfd09 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -106,7 +106,7 @@ public int Quality /// /// Gets the comments. /// - public ICollection>? Comments { get; } + public IList> Comments { get; } /// public IDeepCloneable DeepClone() => new JpegMetadata(this); diff --git a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs index 0c66fcbdd7..7330e74b79 100644 --- a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs @@ -18,37 +18,4 @@ public static partial class MetadataExtensions /// The metadata this method extends. /// The . public static JpegMetadata GetJpegMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(JpegFormat.Instance); - - /// - /// Sets the comment in - /// - /// The metadata this method extends. - /// The index of comment to be inserted to. - /// The comment string. - public static void SetComment(this JpegMetadata metadata, int index, string comment) - { - if (metadata.Comments == null) - { - return; - } - - ASCIIEncoding encoding = new(); - byte[] bytes = encoding.GetBytes(comment); - List>? comments = metadata.Comments as List>; - comments?.Insert(index, encoding.GetChars(bytes)); - } - - /// - /// Gets the comments from - /// - /// The metadata this method extends. - /// The index of comment. - /// The IEnumerable string of comments. - public static string? GetComment(this JpegMetadata metadata, int index) => metadata.Comments?.ElementAtOrDefault(index).ToString(); - - /// - /// Clears comments - /// - /// The . - public static void ClearComments(this JpegMetadata metadata) => metadata.Comments?.Clear(); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index fb37a956d0..369e71abfc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -434,8 +434,8 @@ public void JpegDecoder_DecodeMetadataComment(TestImageProvider using Image image = provider.GetImage(JpegDecoder.Instance); JpegMetadata metadata = image.Metadata.GetJpegMetadata(); - Assert.Equal(1, metadata.Comments?.Count); - Assert.Equal(expectedComment, metadata.GetComment(0)); + Assert.Equal(1, metadata.Comments.Count); + Assert.Equal(expectedComment.ToCharArray(), metadata.Comments.ElementAtOrDefault(0)); image.DebugSave(provider); image.CompareToOriginal(provider); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index bd68eaf208..8cc64acea3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -172,7 +172,7 @@ public void Encode_PreservesComments(TestImageProvider provider) JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(1, actual.Comments.Count); - Assert.Equal("TEST COMMENT", actual.Comments.ElementAt(0).ToString()); + Assert.Equal("TEST COMMENT", actual.Comments.ElementAtOrDefault(0).ToString()); } [Fact] @@ -184,8 +184,8 @@ public void Encode_SavesMultipleComments() using var memStream = new MemoryStream(); // act - meta.SetComment(0, "First comment"); - meta.SetComment(1, "Second Comment"); + meta.Comments.Add("First comment".ToCharArray()); + meta.Comments.Add("Second Comment".ToCharArray()); input.Save(memStream, JpegEncoder); // assert @@ -193,9 +193,9 @@ public void Encode_SavesMultipleComments() using Image output = Image.Load(memStream); JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); - Assert.Equal(2, actual.Comments?.Count); - Assert.Equal(meta.Comments?.ElementAt(0).ToString(), actual.Comments?.ElementAt(0).ToString()); - Assert.Equal(meta.Comments?.ElementAt(1).ToString(), actual.Comments?.ElementAt(1).ToString()); + Assert.Equal(2, actual.Comments.Count); + Assert.Equal(meta.Comments.ElementAtOrDefault(0).ToString(), actual.Comments.ElementAtOrDefault(0).ToString()); + Assert.Equal(meta.Comments.ElementAtOrDefault(1).ToString(), actual.Comments.ElementAtOrDefault(1).ToString()); } [Theory] From c10863fee66d11cd4d9d0b030c4f7235dc3c1b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Mutnia=C5=84ski?= Date: Tue, 6 Feb 2024 15:33:11 +0100 Subject: [PATCH 6/9] Replace Memory with string --- .../Formats/Jpeg/JpegDecoderCore.cs | 5 ++--- .../Formats/Jpeg/JpegEncoderCore.cs | 22 +++++++++---------- src/ImageSharp/Formats/Jpeg/JpegMetadata.cs | 4 ++-- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 2 +- .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 10 ++++----- .../Formats/Jpg/JpegMetadataTests.cs | 10 ++++----- 6 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index d6b40fa7f7..5f3fa33fa6 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -524,13 +524,12 @@ public void Dispose() private void ProcessComMarker(BufferedReadStream stream, int markerContentByteSize) { Span temp = new byte[markerContentByteSize]; - char[] chars = new char[markerContentByteSize]; JpegMetadata metadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance); stream.Read(temp); - Encoding.ASCII.GetChars(temp, chars); + string comment = Encoding.ASCII.GetString(temp); - metadata.Comments.Add(chars); + metadata.Comments.Add(comment); } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 4dc9202070..8658f6b25f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -186,23 +186,23 @@ private void WriteComment(JpegMetadata metadata) for (int i = 0; i < metadata.Comments.Count; i++) { - Memory chars = metadata.Comments[i]; + string comment = metadata.Comments[i]; - if (chars.Length > maxCommentLength) + if (comment.Length > maxCommentLength) { - Memory splitComment = chars.Slice(maxCommentLength, chars.Length - maxCommentLength); + string splitComment = comment.Substring(maxCommentLength, comment.Length - maxCommentLength); metadata.Comments.Insert(i + 1, splitComment); // We don't want to keep the extra bytes - chars = chars.Slice(0, maxCommentLength); + comment = comment.Substring(0, maxCommentLength); } - int commentLength = chars.Length + 4; + int commentLength = comment.Length + 4; - Span comment = new byte[commentLength]; - Span markers = comment.Slice(0, 2); - Span payloadSize = comment.Slice(2, 2); - Span payload = comment.Slice(4, chars.Length); + Span commentSpan = new byte[commentLength]; + Span markers = commentSpan.Slice(0, 2); + Span payloadSize = commentSpan.Slice(2, 2); + Span payload = commentSpan.Slice(4, comment.Length); // Beginning of comment ff fe markers[0] = JpegConstants.Markers.XFF; @@ -213,9 +213,9 @@ private void WriteComment(JpegMetadata metadata) payloadSize[0] = (byte)((comWithoutMarker >> 8) & 0xFF); payloadSize[1] = (byte)(comWithoutMarker & 0xFF); - Encoding.ASCII.GetBytes(chars.Span, payload); + Encoding.ASCII.GetBytes(comment, payload); - this.outputStream.Write(comment, 0, comment.Length); + this.outputStream.Write(commentSpan, 0, commentSpan.Length); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index bf758dfd09..5b96fdf967 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -15,7 +15,7 @@ public class JpegMetadata : IDeepCloneable /// public JpegMetadata() { - this.Comments = new List>(); + this.Comments = new List(); } /// @@ -106,7 +106,7 @@ public int Quality /// /// Gets the comments. /// - public IList> Comments { get; } + public IList Comments { get; } /// public IDeepCloneable DeepClone() => new JpegMetadata(this); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 369e71abfc..222d1fb8cf 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -435,7 +435,7 @@ public void JpegDecoder_DecodeMetadataComment(TestImageProvider JpegMetadata metadata = image.Metadata.GetJpegMetadata(); Assert.Equal(1, metadata.Comments.Count); - Assert.Equal(expectedComment.ToCharArray(), metadata.Comments.ElementAtOrDefault(0)); + Assert.Equal(expectedComment, metadata.Comments.ElementAtOrDefault(0)); image.DebugSave(provider); image.CompareToOriginal(provider); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index 8cc64acea3..f33234e322 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -172,7 +172,7 @@ public void Encode_PreservesComments(TestImageProvider provider) JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(1, actual.Comments.Count); - Assert.Equal("TEST COMMENT", actual.Comments.ElementAtOrDefault(0).ToString()); + Assert.Equal("TEST COMMENT", actual.Comments.ElementAtOrDefault(0)); } [Fact] @@ -184,8 +184,8 @@ public void Encode_SavesMultipleComments() using var memStream = new MemoryStream(); // act - meta.Comments.Add("First comment".ToCharArray()); - meta.Comments.Add("Second Comment".ToCharArray()); + meta.Comments.Add("First comment"); + meta.Comments.Add("Second Comment"); input.Save(memStream, JpegEncoder); // assert @@ -194,8 +194,8 @@ public void Encode_SavesMultipleComments() JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(2, actual.Comments.Count); - Assert.Equal(meta.Comments.ElementAtOrDefault(0).ToString(), actual.Comments.ElementAtOrDefault(0).ToString()); - Assert.Equal(meta.Comments.ElementAtOrDefault(1).ToString(), actual.Comments.ElementAtOrDefault(1).ToString()); + Assert.Equal(meta.Comments.ElementAtOrDefault(0), actual.Comments.ElementAtOrDefault(0)); + Assert.Equal(meta.Comments.ElementAtOrDefault(1), actual.Comments.ElementAtOrDefault(1)); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index 8b991228b4..64d7edd759 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -64,19 +64,19 @@ public void Comment_EmptyComment() { var meta = new JpegMetadata(); - Assert.True(Array.Empty>().SequenceEqual(meta.Comments)); + Assert.True(Array.Empty().SequenceEqual(meta.Comments)); } [Fact] public void Comment_OnlyComment() { string comment = "test comment"; - var expectedCollection = new Collection> { new(comment.ToCharArray()) }; + var expectedCollection = new Collection { comment }; var meta = new JpegMetadata(); - meta.Comments?.Add(comment.ToCharArray()); + meta.Comments.Add(comment); - Assert.Equal(1, meta.Comments?.Count); - Assert.True(expectedCollection.FirstOrDefault().ToString() == meta.Comments?.FirstOrDefault().ToString()); + Assert.Equal(1, meta.Comments.Count); + Assert.True(expectedCollection.FirstOrDefault() == meta.Comments.FirstOrDefault()); } } From d2251287ceaeb0c827d62b29a651775fd2daaa83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Mutnia=C5=84ski?= Date: Tue, 6 Feb 2024 16:16:35 +0100 Subject: [PATCH 7/9] Introduce JpegComData.cs --- src/ImageSharp/Formats/Jpeg/JpegComData.cs | 32 +++++++++++++++++++ .../Formats/Jpeg/JpegDecoderCore.cs | 2 +- .../Formats/Jpeg/JpegEncoderCore.cs | 4 +-- src/ImageSharp/Formats/Jpeg/JpegMetadata.cs | 4 +-- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 2 +- .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 10 +++--- .../Formats/Jpg/JpegMetadataTests.cs | 6 ++-- 7 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/JpegComData.cs diff --git a/src/ImageSharp/Formats/Jpeg/JpegComData.cs b/src/ImageSharp/Formats/Jpeg/JpegComData.cs new file mode 100644 index 0000000000..26ade0217f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegComData.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Contains JPEG comment +/// +public readonly struct JpegComData +{ + /// + /// Converts string to + /// + /// The comment string. + /// The + public static JpegComData FromString(string value) => new(value.AsMemory()); + + /// + /// Initializes a new instance of the struct. + /// + /// The comment ReadOnlyMemory of chars. + public JpegComData(ReadOnlyMemory value) + => this.Value = value; + + public ReadOnlyMemory Value { get; } + + /// + /// Converts Value to string + /// + /// The comment string. + public override string ToString() => this.Value.ToString(); +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 5f3fa33fa6..e43c20a7d9 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -529,7 +529,7 @@ private void ProcessComMarker(BufferedReadStream stream, int markerContentByteSi stream.Read(temp); string comment = Encoding.ASCII.GetString(temp); - metadata.Comments.Add(comment); + metadata.Comments.Add(JpegComData.FromString(comment)); } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 8658f6b25f..a55435c532 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -186,12 +186,12 @@ private void WriteComment(JpegMetadata metadata) for (int i = 0; i < metadata.Comments.Count; i++) { - string comment = metadata.Comments[i]; + string comment = metadata.Comments[i].ToString(); if (comment.Length > maxCommentLength) { string splitComment = comment.Substring(maxCommentLength, comment.Length - maxCommentLength); - metadata.Comments.Insert(i + 1, splitComment); + metadata.Comments.Insert(i + 1, JpegComData.FromString(splitComment)); // We don't want to keep the extra bytes comment = comment.Substring(0, maxCommentLength); diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index 5b96fdf967..fe1324a862 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -15,7 +15,7 @@ public class JpegMetadata : IDeepCloneable /// public JpegMetadata() { - this.Comments = new List(); + this.Comments = new List(); } /// @@ -106,7 +106,7 @@ public int Quality /// /// Gets the comments. /// - public IList Comments { get; } + public IList Comments { get; } /// public IDeepCloneable DeepClone() => new JpegMetadata(this); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 222d1fb8cf..cbb2befcd4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -435,7 +435,7 @@ public void JpegDecoder_DecodeMetadataComment(TestImageProvider JpegMetadata metadata = image.Metadata.GetJpegMetadata(); Assert.Equal(1, metadata.Comments.Count); - Assert.Equal(expectedComment, metadata.Comments.ElementAtOrDefault(0)); + Assert.Equal(expectedComment, metadata.Comments.ElementAtOrDefault(0).ToString()); image.DebugSave(provider); image.CompareToOriginal(provider); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index f33234e322..e49afedbb5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -172,7 +172,7 @@ public void Encode_PreservesComments(TestImageProvider provider) JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(1, actual.Comments.Count); - Assert.Equal("TEST COMMENT", actual.Comments.ElementAtOrDefault(0)); + Assert.Equal("TEST COMMENT", actual.Comments.ElementAtOrDefault(0).ToString()); } [Fact] @@ -184,8 +184,8 @@ public void Encode_SavesMultipleComments() using var memStream = new MemoryStream(); // act - meta.Comments.Add("First comment"); - meta.Comments.Add("Second Comment"); + meta.Comments.Add(JpegComData.FromString("First comment")); + meta.Comments.Add(JpegComData.FromString("Second Comment")); input.Save(memStream, JpegEncoder); // assert @@ -194,8 +194,8 @@ public void Encode_SavesMultipleComments() JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(2, actual.Comments.Count); - Assert.Equal(meta.Comments.ElementAtOrDefault(0), actual.Comments.ElementAtOrDefault(0)); - Assert.Equal(meta.Comments.ElementAtOrDefault(1), actual.Comments.ElementAtOrDefault(1)); + Assert.Equal(meta.Comments.ElementAtOrDefault(0).ToString(), actual.Comments.ElementAtOrDefault(0).ToString()); + Assert.Equal(meta.Comments.ElementAtOrDefault(1).ToString(), actual.Comments.ElementAtOrDefault(1).ToString()); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index 64d7edd759..e07c42f898 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -64,7 +64,7 @@ public void Comment_EmptyComment() { var meta = new JpegMetadata(); - Assert.True(Array.Empty().SequenceEqual(meta.Comments)); + Assert.True(Array.Empty().SequenceEqual(meta.Comments)); } [Fact] @@ -74,9 +74,9 @@ public void Comment_OnlyComment() var expectedCollection = new Collection { comment }; var meta = new JpegMetadata(); - meta.Comments.Add(comment); + meta.Comments.Add(JpegComData.FromString(comment)); Assert.Equal(1, meta.Comments.Count); - Assert.True(expectedCollection.FirstOrDefault() == meta.Comments.FirstOrDefault()); + Assert.True(expectedCollection.FirstOrDefault() == meta.Comments.FirstOrDefault().ToString()); } } From 8af9a8068e4b4f54a0a33438fc9937609f28ed79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Mutnia=C5=84ski?= Date: Wed, 28 Feb 2024 11:01:01 +0100 Subject: [PATCH 8/9] PR comments changes --- .../Formats/Jpeg/JpegDecoderCore.cs | 11 ++-- .../Formats/Jpeg/JpegEncoderCore.cs | 53 ++++++++++++------- .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 23 ++++++++ 3 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index e43c20a7d9..cf5e449e71 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -523,13 +523,16 @@ public void Dispose() /// The remaining bytes in the segment block. private void ProcessComMarker(BufferedReadStream stream, int markerContentByteSize) { - Span temp = new byte[markerContentByteSize]; + char[] temp = new char[markerContentByteSize]; JpegMetadata metadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance); - stream.Read(temp); - string comment = Encoding.ASCII.GetString(temp); + for (int i = 0; i < markerContentByteSize; i++) + { + int read = stream.ReadByte(); + temp[i] = (char)read; + } - metadata.Comments.Add(JpegComData.FromString(comment)); + metadata.Comments.Add(new JpegComData(temp)); } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index a55435c532..4ef4cea2d5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -3,6 +3,7 @@ #nullable disable using System.Buffers.Binary; +using System.Collections; using System.Text; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -184,39 +185,55 @@ private void WriteComment(JpegMetadata metadata) return; } - for (int i = 0; i < metadata.Comments.Count; i++) + // We don't want to modify original metadata + List comments = new(metadata.Comments); + + int totalPayloadLength = 0; + for (int i = 0; i < comments.Count; i++) { - string comment = metadata.Comments[i].ToString(); + JpegComData comment = comments[i]; + ReadOnlyMemory currentComment = comment.Value; - if (comment.Length > maxCommentLength) + if (comment.Value.Length > maxCommentLength) { - string splitComment = comment.Substring(maxCommentLength, comment.Length - maxCommentLength); - metadata.Comments.Insert(i + 1, JpegComData.FromString(splitComment)); + ReadOnlyMemory splitComment = + currentComment.Slice(maxCommentLength, currentComment.Length - maxCommentLength); + comments.Insert(i + 1, new JpegComData(splitComment)); // We don't want to keep the extra bytes - comment = comment.Substring(0, maxCommentLength); + comments[i] = new JpegComData(currentComment.Slice(0, maxCommentLength)); } - int commentLength = comment.Length + 4; + totalPayloadLength += comment.Value.Length + 4; + } + + Span payload = new byte[totalPayloadLength]; + int currentCommentStartingIndex = 0; - Span commentSpan = new byte[commentLength]; - Span markers = commentSpan.Slice(0, 2); - Span payloadSize = commentSpan.Slice(2, 2); - Span payload = commentSpan.Slice(4, comment.Length); + for (int i = 0; i < comments.Count; i++) + { + ReadOnlyMemory comment = comments[i].Value; // Beginning of comment ff fe - markers[0] = JpegConstants.Markers.XFF; - markers[1] = JpegConstants.Markers.COM; + payload[currentCommentStartingIndex] = JpegConstants.Markers.XFF; + payload[currentCommentStartingIndex + 1] = JpegConstants.Markers.COM; // Write payload size - int comWithoutMarker = commentLength - 2; - payloadSize[0] = (byte)((comWithoutMarker >> 8) & 0xFF); - payloadSize[1] = (byte)(comWithoutMarker & 0xFF); + int comWithoutMarker = comment.Length + 2; + payload[currentCommentStartingIndex + 2] = (byte)((comWithoutMarker >> 8) & 0xFF); + payload[currentCommentStartingIndex + 3] = (byte)(comWithoutMarker & 0xFF); - Encoding.ASCII.GetBytes(comment, payload); + char[] commentChars = comment.ToArray(); + for (int j = 0; j < commentChars.Length; j++) + { + // Initial 4 bytes are always reserved + payload[4 + currentCommentStartingIndex + j] = (byte)commentChars[j]; + } - this.outputStream.Write(commentSpan, 0, commentSpan.Length); + currentCommentStartingIndex += comment.Length + 4; } + + this.outputStream.Write(payload, 0, payload.Length); } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index e49afedbb5..f0593b462c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -198,6 +198,29 @@ public void Encode_SavesMultipleComments() Assert.Equal(meta.Comments.ElementAtOrDefault(1).ToString(), actual.Comments.ElementAtOrDefault(1).ToString()); } + [Fact] + public void Encode_SaveTooLongComment() + { + // arrange + string longString = new('c', 65534); + using var input = new Image(1, 1); + JpegMetadata meta = input.Metadata.GetJpegMetadata(); + using var memStream = new MemoryStream(); + + // act + meta.Comments.Add(JpegComData.FromString(longString)); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using Image output = Image.Load(memStream); + JpegMetadata actual = output.Metadata.GetJpegMetadata(); + Assert.NotEmpty(actual.Comments); + Assert.Equal(2, actual.Comments.Count); + Assert.Equal(longString[..65533], actual.Comments.ElementAtOrDefault(0).ToString()); + Assert.Equal("c", actual.Comments.ElementAtOrDefault(1).ToString()); + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] From d8484da7399659a78c044545589a4c5ec67bbda0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 13 Mar 2024 15:10:20 +1000 Subject: [PATCH 9/9] Remove allocations and cleanup. --- src/ImageSharp/Formats/Jpeg/JpegComData.cs | 24 ++-- .../Formats/Jpeg/JpegDecoderCore.cs | 9 +- .../Formats/Jpeg/JpegEncoderCore.cs | 77 +++++-------- .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 104 ++++++++---------- 4 files changed, 92 insertions(+), 122 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegComData.cs b/src/ImageSharp/Formats/Jpeg/JpegComData.cs index 26ade0217f..4e832d9030 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegComData.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegComData.cs @@ -1,32 +1,32 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Jpeg; /// -/// Contains JPEG comment +/// Represents a JPEG comment /// public readonly struct JpegComData { - /// - /// Converts string to - /// - /// The comment string. - /// The - public static JpegComData FromString(string value) => new(value.AsMemory()); - /// /// Initializes a new instance of the struct. /// - /// The comment ReadOnlyMemory of chars. + /// The comment buffer. public JpegComData(ReadOnlyMemory value) => this.Value = value; + /// + /// Gets the value. + /// public ReadOnlyMemory Value { get; } /// - /// Converts Value to string + /// Converts string to /// - /// The comment string. + /// The comment string. + /// The + public static JpegComData FromString(string value) => new(value.AsMemory()); + + /// public override string ToString() => this.Value.ToString(); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index cf5e449e71..906505b76a 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -6,7 +6,6 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; @@ -481,6 +480,8 @@ internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralC break; case JpegConstants.Markers.APP15: + stream.Skip(markerContentByteSize); + break; case JpegConstants.Markers.COM: this.ProcessComMarker(stream, markerContentByteSize); break; @@ -523,16 +524,16 @@ public void Dispose() /// The remaining bytes in the segment block. private void ProcessComMarker(BufferedReadStream stream, int markerContentByteSize) { - char[] temp = new char[markerContentByteSize]; + char[] chars = new char[markerContentByteSize]; JpegMetadata metadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance); for (int i = 0; i < markerContentByteSize; i++) { int read = stream.ReadByte(); - temp[i] = (char)read; + chars[i] = (char)read; } - metadata.Comments.Add(new JpegComData(temp)); + metadata.Comments.Add(new JpegComData(chars)); } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 4ef4cea2d5..243bbe051d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -2,9 +2,8 @@ // Licensed under the Six Labors Split License. #nullable disable +using System.Buffers; using System.Buffers.Binary; -using System.Collections; -using System.Text; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; @@ -27,6 +26,9 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals /// private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs(); + /// + /// The current calling encoder. + /// private readonly JpegEncoder encoder; /// @@ -92,7 +94,7 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteProfiles(metadata, buffer); // Write comments - this.WriteComment(jpegMetadata); + this.WriteComments(image.Configuration, jpegMetadata); // Write the image dimensions. this.WriteStartOfFrame(image.Width, image.Height, frameConfig, buffer); @@ -173,67 +175,48 @@ private void WriteJfifApplicationHeader(ImageMetadata meta, Span buffer) } /// - /// Writes comment + /// Writes the COM tags. /// + /// The configuration. /// The image metadata. - private void WriteComment(JpegMetadata metadata) + private void WriteComments(Configuration configuration, JpegMetadata metadata) { - int maxCommentLength = 65533; - if (metadata.Comments.Count == 0) { return; } - // We don't want to modify original metadata - List comments = new(metadata.Comments); - - int totalPayloadLength = 0; - for (int i = 0; i < comments.Count; i++) + const int maxCommentLength = 65533; + using IMemoryOwner bufferOwner = configuration.MemoryAllocator.Allocate(maxCommentLength); + Span buffer = bufferOwner.Memory.Span; + foreach (JpegComData comment in metadata.Comments) { - JpegComData comment = comments[i]; - ReadOnlyMemory currentComment = comment.Value; - - if (comment.Value.Length > maxCommentLength) + int totalLength = comment.Value.Length; + if (totalLength == 0) { - ReadOnlyMemory splitComment = - currentComment.Slice(maxCommentLength, currentComment.Length - maxCommentLength); - comments.Insert(i + 1, new JpegComData(splitComment)); - - // We don't want to keep the extra bytes - comments[i] = new JpegComData(currentComment.Slice(0, maxCommentLength)); + continue; } - totalPayloadLength += comment.Value.Length + 4; - } - - Span payload = new byte[totalPayloadLength]; - int currentCommentStartingIndex = 0; - - for (int i = 0; i < comments.Count; i++) - { - ReadOnlyMemory comment = comments[i].Value; + // Loop through and split the comment into multiple comments if the comment length + // is greater than the maximum allowed length. + while (totalLength > 0) + { + int currentLength = Math.Min(totalLength, maxCommentLength); - // Beginning of comment ff fe - payload[currentCommentStartingIndex] = JpegConstants.Markers.XFF; - payload[currentCommentStartingIndex + 1] = JpegConstants.Markers.COM; + // Write the marker header. + this.WriteMarkerHeader(JpegConstants.Markers.COM, currentLength + 2, buffer); - // Write payload size - int comWithoutMarker = comment.Length + 2; - payload[currentCommentStartingIndex + 2] = (byte)((comWithoutMarker >> 8) & 0xFF); - payload[currentCommentStartingIndex + 3] = (byte)(comWithoutMarker & 0xFF); + ReadOnlySpan commentValue = comment.Value.Span.Slice(comment.Value.Length - totalLength, currentLength); + for (int i = 0; i < commentValue.Length; i++) + { + buffer[i] = (byte)commentValue[i]; + } - char[] commentChars = comment.ToArray(); - for (int j = 0; j < commentChars.Length; j++) - { - // Initial 4 bytes are always reserved - payload[4 + currentCommentStartingIndex + j] = (byte)commentChars[j]; + // Write the comment. + this.outputStream.Write(buffer, 0, currentLength); + totalLength -= currentLength; } - - currentCommentStartingIndex += comment.Length + 4; } - - this.outputStream.Write(payload, 0, payload.Length); } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index f0593b462c..f06fbe9635 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -32,19 +32,19 @@ public partial class JpegEncoderTests public void Encode_PreservesIptcProfile() { // arrange - using var input = new Image(1, 1); - var expectedProfile = new IptcProfile(); + using Image input = new(1, 1); + IptcProfile expectedProfile = new(); expectedProfile.SetValue(IptcTag.Country, "ESPAÑA"); expectedProfile.SetValue(IptcTag.City, "unit-test-city"); input.Metadata.IptcProfile = expectedProfile; // act - using var memStream = new MemoryStream(); + using MemoryStream memStream = new(); input.Save(memStream, JpegEncoder); // assert memStream.Position = 0; - using var output = Image.Load(memStream); + using Image output = Image.Load(memStream); IptcProfile actual = output.Metadata.IptcProfile; Assert.NotNull(actual); IEnumerable values = expectedProfile.Values; @@ -55,17 +55,17 @@ public void Encode_PreservesIptcProfile() public void Encode_PreservesExifProfile() { // arrange - using var input = new Image(1, 1); + using Image input = new(1, 1); input.Metadata.ExifProfile = new ExifProfile(); input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); // act - using var memStream = new MemoryStream(); + using MemoryStream memStream = new(); input.Save(memStream, JpegEncoder); // assert memStream.Position = 0; - using var output = Image.Load(memStream); + using Image output = Image.Load(memStream); ExifProfile actual = output.Metadata.ExifProfile; Assert.NotNull(actual); IReadOnlyList values = input.Metadata.ExifProfile.Values; @@ -76,16 +76,16 @@ public void Encode_PreservesExifProfile() public void Encode_PreservesIccProfile() { // arrange - using var input = new Image(1, 1); + using Image input = new(1, 1); input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); // act - using var memStream = new MemoryStream(); + using MemoryStream memStream = new(); input.Save(memStream, JpegEncoder); // assert memStream.Position = 0; - using var output = Image.Load(memStream); + using Image output = Image.Load(memStream); IccProfile actual = output.Metadata.IccProfile; Assert.NotNull(actual); IccProfile values = input.Metadata.IccProfile; @@ -99,12 +99,10 @@ public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageP { Exception ex = Record.Exception(() => { - var encoder = new JpegEncoder(); - using (var stream = new MemoryStream()) - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.Save(stream, encoder); - } + JpegEncoder encoder = new(); + using MemoryStream stream = new(); + using Image image = provider.GetImage(JpegDecoder.Instance); + image.Save(stream, encoder); }); Assert.Null(ex); @@ -114,23 +112,17 @@ public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageP [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); - } - } - } + TestFile testFile = TestFile.Create(imagePath); + using Image input = testFile.CreateRgba32Image(); + using MemoryStream memStream = new(); + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using Image output = Image.Load(memStream); + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } [Theory] @@ -138,20 +130,14 @@ public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolut public void Encode_PreservesQuality(string imagePath, int quality) { TestFile 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); - } - } - } + using Image input = testFile.CreateRgba32Image(); + using MemoryStream memStream = new(); + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using Image output = Image.Load(memStream); + JpegMetadata meta = output.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); } [Theory] @@ -161,7 +147,7 @@ public void Encode_PreservesComments(TestImageProvider provider) { // arrange using Image input = provider.GetImage(JpegDecoder.Instance); - using var memStream = new MemoryStream(); + using MemoryStream memStream = new(); // act input.Save(memStream, JpegEncoder); @@ -172,16 +158,16 @@ public void Encode_PreservesComments(TestImageProvider provider) JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(1, actual.Comments.Count); - Assert.Equal("TEST COMMENT", actual.Comments.ElementAtOrDefault(0).ToString()); + Assert.Equal("TEST COMMENT", actual.Comments[0].ToString()); } [Fact] public void Encode_SavesMultipleComments() { // arrange - using var input = new Image(1, 1); + using Image input = new(1, 1); JpegMetadata meta = input.Metadata.GetJpegMetadata(); - using var memStream = new MemoryStream(); + using MemoryStream memStream = new(); // act meta.Comments.Add(JpegComData.FromString("First comment")); @@ -194,8 +180,8 @@ public void Encode_SavesMultipleComments() JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(2, actual.Comments.Count); - Assert.Equal(meta.Comments.ElementAtOrDefault(0).ToString(), actual.Comments.ElementAtOrDefault(0).ToString()); - Assert.Equal(meta.Comments.ElementAtOrDefault(1).ToString(), actual.Comments.ElementAtOrDefault(1).ToString()); + Assert.Equal(meta.Comments[0].ToString(), actual.Comments[0].ToString()); + Assert.Equal(meta.Comments[1].ToString(), actual.Comments[1].ToString()); } [Fact] @@ -203,9 +189,9 @@ public void Encode_SaveTooLongComment() { // arrange string longString = new('c', 65534); - using var input = new Image(1, 1); + using Image input = new(1, 1); JpegMetadata meta = input.Metadata.GetJpegMetadata(); - using var memStream = new MemoryStream(); + using MemoryStream memStream = new(); // act meta.Comments.Add(JpegComData.FromString(longString)); @@ -217,8 +203,8 @@ public void Encode_SaveTooLongComment() JpegMetadata actual = output.Metadata.GetJpegMetadata(); Assert.NotEmpty(actual.Comments); Assert.Equal(2, actual.Comments.Count); - Assert.Equal(longString[..65533], actual.Comments.ElementAtOrDefault(0).ToString()); - Assert.Equal("c", actual.Comments.ElementAtOrDefault(1).ToString()); + Assert.Equal(longString[..65533], actual.Comments[0].ToString()); + Assert.Equal("c", actual.Comments[1].ToString()); } [Theory] @@ -231,14 +217,14 @@ public void Encode_PreservesColorType(TestImageProvider provider { // arrange using Image input = provider.GetImage(JpegDecoder.Instance); - using var memoryStream = new MemoryStream(); + using MemoryStream memoryStream = new(); // act input.Save(memoryStream, JpegEncoder); // assert memoryStream.Position = 0; - using var output = Image.Load(memoryStream); + using Image output = Image.Load(memoryStream); JpegMetadata meta = output.Metadata.GetJpegMetadata(); Assert.Equal(expectedColorType, meta.ColorType); }