Browse Source

Prevent memory allocations

pull/1918/head
Ynse Hoornenborg 4 years ago
parent
commit
1ee34bacb5
  1. 2
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  2. 55
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  3. 2
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  4. 2
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  5. 2
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  6. 2
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  7. 2
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  8. 17
      src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs
  9. 6
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  10. 2
      tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
  11. 2
      tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs
  12. 12
      tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs

2
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -339,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Application Extension: XMP Profile. // Application Extension: XMP Profile.
if (xmpProfile != null) if (xmpProfile != null)
{ {
var xmpExtension = new GifXmpApplicationExtension(xmpProfile.ToByteArray()); var xmpExtension = new GifXmpApplicationExtension(xmpProfile.Data);
this.WriteExtension(xmpExtension, stream); this.WriteExtension(xmpExtension, stream);
} }
} }

55
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -681,9 +681,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param> /// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp1Marker(BufferedReadStream stream, int remaining) private void ProcessApp1Marker(BufferedReadStream stream, int remaining)
{ {
const int Exif00 = 6; const int ExifMarkerLength = 6;
const int XmpNsLength = 29; const int XmpMarkerLength = 29;
if (remaining < Exif00 || this.IgnoreMetadata) if (remaining < ExifMarkerLength || this.IgnoreMetadata)
{ {
// Skip the application header length // Skip the application header length
stream.Skip(remaining); stream.Skip(remaining);
@ -695,38 +695,55 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length.");
} }
byte[] profile = new byte[remaining]; // XMP marker is the longest, so read at least that many bytes into temp.
stream.Read(profile, 0, remaining); stream.Read(this.temp, 0, ExifMarkerLength);
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker))
{ {
remaining -= ExifMarkerLength;
this.isExif = true; this.isExif = true;
byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining);
if (this.exifData is null) if (this.exifData is null)
{ {
// The first 6 bytes (Exif00) will be skipped, because this is Jpeg specific this.exifData = profile;
this.exifData = profile.AsSpan(Exif00).ToArray();
} }
else else
{ {
// If the EXIF information exceeds 64K, it will be split over multiple APP1 markers // 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; stream.Read(this.temp, 0, XmpMarkerLength - ExifMarkerLength);
if (this.xmpData is null) remaining -= XmpMarkerLength;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(ExifMarkerLength)))
{ {
// The first 29 bytes will be skipped, because this is Jpeg specific this.isXmp = true;
this.xmpData = profile.AsSpan(XmpNsLength).ToArray(); byte[] profile = new byte[remaining];
} stream.Read(profile, 0, remaining);
else
{ if (this.xmpData is null)
// If the XMP information exceeds 64K, it will be split over multiple APP1 markers {
this.ExtendProfile(ref this.xmpData, profile.AsSpan(XmpNsLength).ToArray()); 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);
} }
/// <summary> /// <summary>

2
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -485,7 +485,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
const int Max = 65533; const int Max = 65533;
const int MaxData = Max - XmpOverheadLength; const int MaxData = Max - XmpOverheadLength;
byte[] data = xmpProfile.ToByteArray(); byte[] data = xmpProfile.Data;
if (data is null || data.Length == 0) if (data is null || data.Length == 0)
{ {

2
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -673,7 +673,7 @@ namespace SixLabors.ImageSharp.Formats.Png
return; return;
} }
var xmpData = meta.XmpProfile.ToByteArray(); var xmpData = meta.XmpProfile.Data;
if (xmpData.Length == 0) if (xmpData.Length == 0)
{ {

2
src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs

@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{ {
var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte) var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte)
{ {
Value = xmpProfile.ToByteArray() Value = xmpProfile.Data
}; };
this.Collector.Add(xmp); this.Collector.Add(xmp);

2
src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs

@ -427,7 +427,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
{ {
isVp8X = true; isVp8X = true;
riffSize += ExtendedFileChunkSize; riffSize += ExtendedFileChunkSize;
xmpBytes = xmpProfile.ToByteArray(); xmpBytes = xmpProfile.Data;
riffSize += this.MetadataChunkSize(xmpBytes); riffSize += this.MetadataChunkSize(xmpBytes);
} }

2
src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs

@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
{ {
isVp8X = true; isVp8X = true;
riffSize += ExtendedFileChunkSize; riffSize += ExtendedFileChunkSize;
xmpBytes = xmpProfile.ToByteArray(); xmpBytes = xmpProfile.Data;
riffSize += this.MetadataChunkSize(xmpBytes); riffSize += this.MetadataChunkSize(xmpBytes);
} }

17
src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs

@ -14,8 +14,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// </summary> /// </summary>
public sealed class XmpProfile : IDeepCloneable<XmpProfile> public sealed class XmpProfile : IDeepCloneable<XmpProfile>
{ {
private byte[] data;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class. /// Initializes a new instance of the <see cref="XmpProfile"/> class.
/// </summary> /// </summary>
@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// Initializes a new instance of the <see cref="XmpProfile"/> class. /// Initializes a new instance of the <see cref="XmpProfile"/> class.
/// </summary> /// </summary>
/// <param name="data">The UTF8 encoded byte array to read the XMP profile from.</param> /// <param name="data">The UTF8 encoded byte array to read the XMP profile from.</param>
public XmpProfile(byte[] data) => this.data = data; public XmpProfile(byte[] data) => this.Data = data;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class /// Initializes a new instance of the <see cref="XmpProfile"/> class
@ -39,16 +37,21 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
{ {
Guard.NotNull(other, nameof(other)); Guard.NotNull(other, nameof(other));
this.data = other.ToByteArray(); this.Data = other.Data;
} }
/// <summary>
/// Gets the XMP raw data byte array.
/// </summary>
internal byte[] Data { get; private set; }
/// <summary> /// <summary>
/// Gets the raw XML document containing the XMP profile. /// Gets the raw XML document containing the XMP profile.
/// </summary> /// </summary>
/// <returns>The <see cref="XDocument"/></returns> /// <returns>The <see cref="XDocument"/></returns>
public XDocument GetDocument() public XDocument GetDocument()
{ {
byte[] byteArray = this.ToByteArray(); byte[] byteArray = this.Data;
if (byteArray is null) if (byteArray is null)
{ {
return null; return null;
@ -75,8 +78,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
public byte[] ToByteArray() public byte[] ToByteArray()
{ {
byte[] result = new byte[this.data.Length]; byte[] result = new byte[this.Data.Length];
this.data.AsSpan().CopyTo(result); this.Data.AsSpan().CopyTo(result);
return result; return result;
} }

6
tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{ {
Assert.NotNull(rootFrameMetaData.XmpProfile); Assert.NotNull(rootFrameMetaData.XmpProfile);
Assert.NotNull(rootFrameMetaData.ExifProfile); 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); 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.Width);
Assert.Equal(32, rootFrame.Height); Assert.Equal(32, rootFrame.Height);
Assert.NotNull(rootFrame.Metadata.XmpProfile); 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; ExifProfile exifProfile = rootFrame.Metadata.ExifProfile;
TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata();
@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.NotNull(xmpProfileInput); Assert.NotNull(xmpProfileInput);
Assert.NotNull(encodedImageXmpProfile); 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("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value);
Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value); Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value);

2
tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs

@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
else else
{ {
Assert.NotNull(image.Metadata.XmpProfile); Assert.NotNull(image.Metadata.XmpProfile);
Assert.NotEmpty(image.Metadata.XmpProfile.ToByteArray()); Assert.NotEmpty(image.Metadata.XmpProfile.Data);
} }
} }

2
tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs

@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata
Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile));
Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count);
Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); 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.GetGifMetadata().Equals(clone.GetGifMetadata()));
Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); Assert.False(metaData.IccProfile.Equals(clone.IccProfile));
Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile));

12
tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs

@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray()); Assert.Equal(original.Data, actual.Data);
} }
[Fact] [Fact]
@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray()); Assert.Equal(original.Data, actual.Data);
} }
[Fact] [Fact]
@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray()); Assert.Equal(original.Data, actual.Data);
} }
[Fact] [Fact]
@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray()); Assert.Equal(original.Data, actual.Data);
} }
[Fact] [Fact]
@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray()); Assert.Equal(original.Data, actual.Data);
} }
[Fact] [Fact]
@ -225,7 +225,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray()); Assert.Equal(original.Data, actual.Data);
} }
private static void XmpProfileContainsExpectedValues(XmpProfile xmp) private static void XmpProfileContainsExpectedValues(XmpProfile xmp)

Loading…
Cancel
Save