From 1ee34bacb5c94259b5d9429a36f82e64db3c052d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 7 Jan 2022 20:20:49 +0100 Subject: [PATCH] Prevent memory allocations --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 +- .../Formats/Jpeg/JpegDecoderCore.cs | 55 ++++++++++++------- .../Formats/Jpeg/JpegEncoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- .../Tiff/TiffEncoderEntriesCollector.cs | 2 +- .../Formats/Webp/BitWriter/Vp8BitWriter.cs | 2 +- .../Formats/Webp/BitWriter/Vp8LBitWriter.cs | 2 +- .../Metadata/Profiles/XMP/XmpProfile.cs | 17 +++--- .../Formats/Tiff/TiffMetadataTests.cs | 6 +- .../Formats/WebP/WebpMetaDataTests.cs | 2 +- .../Metadata/ImageFrameMetadataTests.cs | 2 +- .../Metadata/Profiles/XMP/XmpProfileTests.cs | 12 ++-- 12 files changed, 63 insertions(+), 43 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 3f51fa639..17dd03cce 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -339,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Gif // Application Extension: XMP Profile. if (xmpProfile != null) { - var xmpExtension = new GifXmpApplicationExtension(xmpProfile.ToByteArray()); + var xmpExtension = new GifXmpApplicationExtension(xmpProfile.Data); this.WriteExtension(xmpExtension, stream); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 61c5d7c66..7f076211b 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -681,9 +681,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The remaining bytes in the segment block. private void ProcessApp1Marker(BufferedReadStream stream, int remaining) { - const int Exif00 = 6; - const int XmpNsLength = 29; - if (remaining < Exif00 || this.IgnoreMetadata) + const int ExifMarkerLength = 6; + const int XmpMarkerLength = 29; + if (remaining < ExifMarkerLength || this.IgnoreMetadata) { // Skip the application header length stream.Skip(remaining); @@ -695,38 +695,55 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); } - byte[] profile = new byte[remaining]; - stream.Read(profile, 0, remaining); + // XMP marker is the longest, so read at least that many bytes into temp. + stream.Read(this.temp, 0, ExifMarkerLength); - if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker)) { + remaining -= ExifMarkerLength; this.isExif = true; + byte[] profile = new byte[remaining]; + stream.Read(profile, 0, remaining); + if (this.exifData is null) { - // The first 6 bytes (Exif00) will be skipped, because this is Jpeg specific - this.exifData = profile.AsSpan(Exif00).ToArray(); + this.exifData = profile; } else { // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers - this.ExtendProfile(ref this.exifData, profile.AsSpan(Exif00).ToArray()); + this.ExtendProfile(ref this.exifData, profile); } + + remaining = 0; } - if (ProfileResolver.IsProfile(profile, ProfileResolver.XmpMarker)) + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength))) { - this.isXmp = true; - if (this.xmpData is null) + stream.Read(this.temp, 0, XmpMarkerLength - ExifMarkerLength); + remaining -= XmpMarkerLength; + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(ExifMarkerLength))) { - // The first 29 bytes will be skipped, because this is Jpeg specific - this.xmpData = profile.AsSpan(XmpNsLength).ToArray(); - } - else - { - // If the XMP information exceeds 64K, it will be split over multiple APP1 markers - this.ExtendProfile(ref this.xmpData, profile.AsSpan(XmpNsLength).ToArray()); + this.isXmp = true; + byte[] profile = new byte[remaining]; + stream.Read(profile, 0, remaining); + + if (this.xmpData is null) + { + this.xmpData = profile; + } + else + { + // If the XMP information exceeds 64K, it will be split over multiple APP1 markers + this.ExtendProfile(ref this.xmpData, profile); + } + + remaining = 0; } } + + // Skip over any remaining bytes of this header. + stream.Skip(remaining); } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index b1125c8e4..a3cff8f31 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -485,7 +485,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg const int Max = 65533; const int MaxData = Max - XmpOverheadLength; - byte[] data = xmpProfile.ToByteArray(); + byte[] data = xmpProfile.Data; if (data is null || data.Length == 0) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 3bb63a24b..cc51d78ec 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -673,7 +673,7 @@ namespace SixLabors.ImageSharp.Formats.Png return; } - var xmpData = meta.XmpProfile.ToByteArray(); + var xmpData = meta.XmpProfile.Data; if (xmpData.Length == 0) { diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 8be029788..e54d029ab 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte) { - Value = xmpProfile.ToByteArray() + Value = xmpProfile.Data }; this.Collector.Add(xmp); diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs index 26942472f..4e91bedb0 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs @@ -427,7 +427,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { isVp8X = true; riffSize += ExtendedFileChunkSize; - xmpBytes = xmpProfile.ToByteArray(); + xmpBytes = xmpProfile.Data; riffSize += this.MetadataChunkSize(xmpBytes); } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs index d4d4f196c..d41224f90 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { isVp8X = true; riffSize += ExtendedFileChunkSize; - xmpBytes = xmpProfile.ToByteArray(); + xmpBytes = xmpProfile.Data; riffSize += this.MetadataChunkSize(xmpBytes); } diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs index 144d93774..8fba243ce 100644 --- a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs @@ -14,8 +14,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// public sealed class XmpProfile : IDeepCloneable { - private byte[] data; - /// /// Initializes a new instance of the class. /// @@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// Initializes a new instance of the class. /// /// The UTF8 encoded byte array to read the XMP profile from. - public XmpProfile(byte[] data) => this.data = data; + public XmpProfile(byte[] data) => this.Data = data; /// /// Initializes a new instance of the class @@ -39,16 +37,21 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp { Guard.NotNull(other, nameof(other)); - this.data = other.ToByteArray(); + this.Data = other.Data; } + /// + /// Gets the XMP raw data byte array. + /// + internal byte[] Data { get; private set; } + /// /// Gets the raw XML document containing the XMP profile. /// /// The public XDocument GetDocument() { - byte[] byteArray = this.ToByteArray(); + byte[] byteArray = this.Data; if (byteArray is null) { return null; @@ -75,8 +78,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// The public byte[] ToByteArray() { - byte[] result = new byte[this.data.Length]; - this.data.AsSpan().CopyTo(result); + byte[] result = new byte[this.Data.Length]; + this.Data.AsSpan().CopyTo(result); return result; } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 8f5a5f76f..6a47a9577 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { Assert.NotNull(rootFrameMetaData.XmpProfile); Assert.NotNull(rootFrameMetaData.ExifProfile); - Assert.Equal(2599, rootFrameMetaData.XmpProfile.ToByteArray().Length); + Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); } } @@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(32, rootFrame.Width); Assert.Equal(32, rootFrame.Height); Assert.NotNull(rootFrame.Metadata.XmpProfile); - Assert.Equal(2599, rootFrame.Metadata.XmpProfile.ToByteArray().Length); + Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); @@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.NotNull(xmpProfileInput); Assert.NotNull(encodedImageXmpProfile); - Assert.Equal(xmpProfileInput.ToByteArray(), encodedImageXmpProfile.ToByteArray()); + Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data); Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value); Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 79c4ee57a..7fba86b4f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp else { Assert.NotNull(image.Metadata.XmpProfile); - Assert.NotEmpty(image.Metadata.XmpProfile.ToByteArray()); + Assert.NotEmpty(image.Metadata.XmpProfile.Data); } } diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index e5918a9ea..dd8ae3d5a 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); - Assert.True(metaData.XmpProfile.ToByteArray().Length.Equals(clone.XmpProfile.ToByteArray().Length)); + Assert.True(metaData.XmpProfile.Data.Equals(clone.XmpProfile.Data)); Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index f1c5edc54..81dad699a 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -225,7 +225,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } private static void XmpProfileContainsExpectedValues(XmpProfile xmp)