Browse Source

New, faster HexStringToBytes implementation based off the reference source implementation of Convert.FromHexString(): https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991

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

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

@ -1032,7 +1032,7 @@ namespace SixLabors.ImageSharp.Formats.Png
tempExifBuf = new byte[exifHeader.Length]; tempExifBuf = new byte[exifHeader.Length];
} }
HexStringToBytes(dataSpan.Slice(0, exifHeader.Length * 2), tempExifBuf.AsSpan()); HexStringToBytes(dataSpan.Slice(0, exifHeader.Length * 2), tempExifBuf);
if (!tempExifBuf.AsSpan().Slice(0, exifHeader.Length).SequenceEqual(exifHeader)) if (!tempExifBuf.AsSpan().Slice(0, exifHeader.Length).SequenceEqual(exifHeader))
{ {
// Exif header in the hex data is not valid // Exif header in the hex data is not valid
@ -1103,51 +1103,90 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
/// <summary> /// <summary>
/// Parses a hexadecimal string into a byte array without allocations. /// Parses a hexadecimal string into a byte array without allocations. Throws on non-hexadecimal character.
/// Adapted from https://stackoverflow.com/a/9995303/871842 /// Adapted from https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991.
/// </summary> /// </summary>
/// <param name="hexString">The hexadecimal string to parse.</param> /// <param name="chars">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> /// <param name="bytes">The destination for the parsed bytes. Must be at least <paramref name="chars"/>.Length / 2 bytes long.</param>
/// <returns>The number of bytes written to <paramref name="outputBytes"/>.</returns> /// <returns>The number of bytes written to <paramref name="bytes"/>.</returns>
private static int HexStringToBytes(ReadOnlySpan<char> hexString, Span<byte> outputBytes) private static int HexStringToBytes(ReadOnlySpan<char> chars, Span<byte> bytes)
{ {
if ((hexString.Length % 2) != 0) if ((chars.Length % 2) != 0)
{ {
throw new ArgumentException("Input string length must be a multiple of 2", nameof(hexString)); throw new ArgumentException("Input string length must be a multiple of 2", nameof(chars));
} }
if ((outputBytes.Length * 2) < hexString.Length) if ((bytes.Length * 2) < chars.Length)
{ {
throw new ArgumentException("Output span must be at least half the length of the input string"); throw new ArgumentException("Output span must be at least half the length of the input string");
} }
else
{
// Slightly better performance in the loop below, allows us to skip a bounds check
// while still supporting output buffers that are larger than necessary
bytes = bytes.Slice(0, chars.Length / 2);
}
static int GetHexVal(char hexChar) [MethodImpl(MethodImplOptions.AggressiveInlining)]
static int FromChar(int c)
{ {
if (hexChar >= '0' && hexChar <= '9') // Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.
{ // This doesn't actually allocate.
return hexChar - '0'; ReadOnlySpan<byte> charToHexLookup = new byte[]
}
else if (hexChar >= 'A' && hexChar <= 'F')
{ {
return 10 + (hexChar - 'A'); 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15
} 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31
else if (hexChar >= 'a' && hexChar <= 'f') 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47
{ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63
return 10 + (hexChar - 'a'); 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79
} 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95
else 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 255
};
return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c];
}
// See https://source.dot.net/#System.Private.CoreLib/HexConverter.cs,4681d45a0aa0b361
int i = 0;
int j = 0;
int byteLo = 0;
int byteHi = 0;
while (j < bytes.Length)
{
byteLo = FromChar(chars[i + 1]);
byteHi = FromChar(chars[i]);
// byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern
// is if either byteHi or byteLo was not a hex character.
if ((byteLo | byteHi) == 0xFF)
{ {
throw new ArgumentException($"Invalid hexadecimal value {hexChar}"); break;
} }
bytes[j++] = (byte)((byteHi << 4) | byteLo);
i += 2;
}
if (byteLo == 0xFF)
{
i++;
} }
int inputByteCount = hexString.Length / 2; if ((byteLo | byteHi) == 0xFF)
for (int i = 0; i < inputByteCount; i++)
{ {
outputBytes[i] = (byte)((GetHexVal(hexString[i * 2]) << 4) + GetHexVal(hexString[(i * 2) + 1])); throw new ArgumentException("Input string contained non-hexadecimal characters", nameof(chars));
} }
return inputByteCount; return j;
} }
/// <summary> /// <summary>

Loading…
Cancel
Save