diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs
index c75f9465af..1fee4a8371 100644
--- a/src/ImageSharp/Formats/Png/PngChunk.cs
+++ b/src/ImageSharp/Formats/Png/PngChunk.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Memory;
@@ -10,12 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Png
///
internal readonly struct PngChunk
{
- public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null, uint crc = 0)
+ public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null)
{
this.Length = length;
this.Type = type;
this.Data = data;
- this.Crc = crc;
}
///
@@ -38,20 +37,12 @@ namespace SixLabors.ImageSharp.Formats.Png
///
public IManagedByteBuffer Data { get; }
- ///
- /// Gets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk,
- /// including the chunk type code and chunk data fields, but not including the length field.
- /// The CRC is always present, even for chunks containing no data
- ///
- public uint Crc { get; }
-
///
/// Gets a value indicating whether the given chunk is critical to decoding
///
public bool IsCritical =>
this.Type == PngChunkType.Header ||
this.Type == PngChunkType.Palette ||
- this.Type == PngChunkType.Data ||
- this.Type == PngChunkType.End;
+ this.Type == PngChunkType.Data;
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 037f648f0a..19e8b62fed 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -221,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (image is null)
{
- throw new ImageFormatException("PNG Image does not contain a data chunk");
+ PngThrowHelper.ThrowNoData();
}
return image;
@@ -285,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.Width == 0 && this.header.Height == 0)
{
- throw new ImageFormatException("PNG Image does not contain a header chunk");
+ PngThrowHelper.ThrowNoHeader();
}
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
@@ -407,7 +407,8 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.RgbWithAlpha:
return this.header.BitDepth * 4;
default:
- throw new NotSupportedException("Unsupported PNG color type");
+ PngThrowHelper.ThrowNotSupportedColor();
+ return -1;
}
}
@@ -528,7 +529,8 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
default:
- throw new ImageFormatException("Unknown filter type.");
+ PngThrowHelper.ThrowUnknownFilter();
+ break;
}
this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata);
@@ -601,7 +603,8 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
default:
- throw new ImageFormatException("Unknown filter type.");
+ PngThrowHelper.ThrowUnknownFilter();
+ break;
}
Span rowSpan = image.GetPixelRowSpan(this.currentRow);
@@ -1119,13 +1122,9 @@ namespace SixLabors.ImageSharp.Formats.Png
chunk = new PngChunk(
length: length,
type: type,
- data: this.ReadChunkData(length),
- crc: this.ReadChunkCrc());
+ data: this.ReadChunkData(length));
- if (chunk.IsCritical)
- {
- this.ValidateChunk(chunk);
- }
+ this.ValidateChunk(chunk);
return true;
}
@@ -1136,6 +1135,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The .
private void ValidateChunk(in PngChunk chunk)
{
+ if (!chunk.IsCritical)
+ {
+ return;
+ }
+
Span chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
@@ -1144,25 +1148,26 @@ namespace SixLabors.ImageSharp.Formats.Png
this.crc.Update(chunkType);
this.crc.Update(chunk.Data.GetSpan());
- if (this.crc.Value != chunk.Crc)
+ uint crc = this.ReadChunkCrc();
+ if (this.crc.Value != crc)
{
string chunkTypeName = Encoding.ASCII.GetString(chunkType);
-
- throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
+ PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
}
}
///
/// Reads the cycle redundancy chunk from the data.
///
- ///
- /// Thrown if the input stream is not valid or corrupt.
- ///
private uint ReadChunkCrc()
{
- return this.currentStream.Read(this.buffer, 0, 4) == 4
- ? BinaryPrimitives.ReadUInt32BigEndian(this.buffer)
- : throw new ImageFormatException("Image stream is not valid!");
+ uint crc = 0;
+ if (this.currentStream.Read(this.buffer, 0, 4) == 4)
+ {
+ crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
+ }
+
+ return crc;
}
///
@@ -1197,9 +1202,17 @@ namespace SixLabors.ImageSharp.Formats.Png
///
private PngChunkType ReadChunkType()
{
- return this.currentStream.Read(this.buffer, 0, 4) == 4
- ? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer)
- : throw new ImageFormatException("Invalid PNG data.");
+ if (this.currentStream.Read(this.buffer, 0, 4) == 4)
+ {
+ return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
+ }
+ else
+ {
+ PngThrowHelper.ThrowInvalidChunkType();
+
+ // The IDE cannot detect the throw here.
+ return default;
+ }
}
///
diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
new file mode 100644
index 0000000000..bc6093948b
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Png
+{
+ ///
+ /// Cold path optimizations for throwing png format based exceptions.
+ ///
+ internal static class PngThrowHelper
+ {
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNoHeader() => throw new ImageFormatException("PNG Image does not contain a header chunk");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNoData() => throw new ImageFormatException("PNG Image does not contain a data chunk");
+
+ public static void ThrowInvalidChunkType() => throw new ImageFormatException("Invalid PNG data.");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
+
+ public static void ThrowNotSupportedColor() => new NotSupportedException("Unsupported PNG color type");
+
+ public static void ThrowUnknownFilter() => throw new ImageFormatException("Unknown filter type.");
+ }
+}