Browse Source

Removed unnecessary temporary allocations.

pull/1877/head
WINDEV2110EVAL\User 5 years ago
parent
commit
c2b906ee12
  1. 134
      src/ImageSharp/Formats/Png/PngDecoderCore.cs

134
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -997,7 +997,7 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<char> dataSpan = data.AsSpan();
dataSpan = dataSpan.TrimStart();
if (!dataSpan.Slice(0, 4).ToString().Equals("exif", StringComparison.OrdinalIgnoreCase))
if (!StringEquals(dataSpan.Slice(0, 4), "exif".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
// "exif" identifier is missing from the beginning of the text chunk
return;
@ -1006,37 +1006,143 @@ namespace SixLabors.ImageSharp.Formats.Png
// Skip to the data length
dataSpan = dataSpan.Slice(4).TrimStart();
int dataLengthEnd = dataSpan.IndexOf('\n');
int dataLength = int.Parse(dataSpan.Slice(0, dataSpan.IndexOf('\n')).ToString());
int dataLength = ParseInt32(dataSpan.Slice(0, dataSpan.IndexOf('\n')));
// Skip to the hex-encoded data
dataSpan = dataSpan.Slice(dataLengthEnd).Trim();
string dataSpanString = dataSpan.ToString().Replace("\n", string.Empty);
if (dataSpanString.Length != (dataLength * 2))
if (dataLength < ExifHeader.Length)
{
// Invalid length
// Not enough room for the required exif header, this data couldn't possibly be valid
return;
}
// Parse the hex-encoded data into the byte array we are going to hand off to ExifProfile
byte[] dataBlob = new byte[dataLength - ExifHeader.Length];
for (int i = 0; i < dataLength; i++)
byte[] exifBlob = new byte[dataLength - ExifHeader.Length];
try
{
byte parsed = Convert.ToByte(dataSpanString.Substring(i * 2, 2), 16);
if ((uint)i < (uint)ExifHeader.Length)
// Check for the presence of the exif header in the hex-encoded binary data
byte[] tempExifBuf = exifBlob;
if (exifBlob.Length < ExifHeader.Length)
{
// Need to allocate a temporary array, this should be an extremely uncommon (TODO: impossible?) case
tempExifBuf = new byte[ExifHeader.Length];
}
HexStringToBytes(dataSpan.Slice(0, ExifHeader.Length * 2), tempExifBuf.AsSpan());
if (!tempExifBuf.AsSpan().Slice(0, ExifHeader.Length).SequenceEqual(ExifHeader))
{
if (parsed != ExifHeader[i])
// Exif header in the hex data is not valid
return;
}
// Skip over the exif header we just tested
dataSpan = dataSpan.Slice(ExifHeader.Length * 2);
dataLength -= ExifHeader.Length;
// Load the hex-encoded data, one line at a time
for (int i = 0; i < dataLength;)
{
ReadOnlySpan<char> lineSpan = dataSpan;
int newlineIndex = dataSpan.IndexOf('\n');
if (newlineIndex != -1)
{
// Invalid exif header in the actual data blob
return;
lineSpan = dataSpan.Slice(0, newlineIndex);
}
i += HexStringToBytes(lineSpan, exifBlob.AsSpan().Slice(i));
dataSpan = dataSpan.Slice(newlineIndex + 1);
}
}
catch
{
return;
}
this.MergeOrSetExifProfile(metadata, new ExifProfile(exifBlob), replaceExistingKeys: false);
}
private static bool StringEquals(ReadOnlySpan<char> span1, ReadOnlySpan<char> span2, StringComparison comparisonType)
{
#pragma warning disable IDE0022 // Use expression body for methods
#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER
return span1.Equals(span2, comparisonType);
#else
return span1.ToString().Equals(span2.ToString(), comparisonType);
#endif
#pragma warning restore IDE0022 // Use expression body for methods
}
/// <summary>
/// int.Parse() a ReadOnlySpan&lt;char&gt;, with a fallback for older frameworks.
/// </summary>
/// <param name="span">The <see cref="int"/> to parse.</param>
/// <param name="style">The <see cref="System.Globalization.NumberStyles"/> of the integer to parse.</param>
/// <param name="provider">The <see cref="IFormatProvider"/> to use when parsing the integer.</param>
/// <returns>The parsed <see cref="int"/>.</returns>
private static int ParseInt32(
ReadOnlySpan<char> span,
System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer,
IFormatProvider provider = null)
{
#pragma warning disable IDE0022 // Use expression body for methods
#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER
return int.Parse(span, style, provider);
#else
return int.Parse(span.ToString(), style, provider);
#endif
#pragma warning restore IDE0022 // Use expression body for methods
}
/// <summary>
/// Parses a hexadecimal string into a byte array without allocations.
/// Adapted from https://stackoverflow.com/a/9995303/871842
/// </summary>
/// <param name="hexString">The hexadecimal string to parse.</param>
/// <param name="outputBytes">The destination for the parsed bytes. Must be at least <paramref name="hexString"/>.Length / 2 bytes long.</param>
/// <returns>The number of bytes written to <paramref name="outputBytes"/>.</returns>
private static int HexStringToBytes(ReadOnlySpan<char> hexString, Span<byte> outputBytes)
{
if ((hexString.Length % 2) != 0)
{
throw new ArgumentException("Input string length must be a multiple of 2", nameof(hexString));
}
if ((outputBytes.Length * 2) < hexString.Length)
{
throw new ArgumentException("Output span must be at least half the length of the input string");
}
static int GetHexVal(char hexChar)
{
if (hexChar >= '0' && hexChar <= '9')
{
return hexChar - '0';
}
else if (hexChar >= 'A' && hexChar <= 'F')
{
return 10 + (hexChar - 'A');
}
else if (hexChar >= 'a' && hexChar <= 'f')
{
return 10 + (hexChar - 'a');
}
else
{
dataBlob[i - ExifHeader.Length] = parsed;
throw new ArgumentException($"Invalid hexadecimal value {hexChar}");
}
}
this.MergeOrSetExifProfile(metadata, new ExifProfile(dataBlob), replaceExistingKeys: false);
int inputByteCount = hexString.Length / 2;
for (int i = 0; i < inputByteCount; i++)
{
outputBytes[i] = (byte)((GetHexVal(hexString[i * 2]) << 4) + GetHexVal(hexString[(i * 2) + 1]));
}
return inputByteCount;
}
/// <summary>

Loading…
Cancel
Save