diff --git a/src/ImageSharp/Formats/Png/Adam7.cs b/src/ImageSharp/Formats/Png/Adam7.cs
new file mode 100644
index 000000000..4e6485b55
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Adam7.cs
@@ -0,0 +1,56 @@
+// 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
+{
+ ///
+ /// Constants and helper methods for the Adam7 interlacing algorithm.
+ ///
+ internal static class Adam7
+ {
+ ///
+ /// The amount to increment when processing each column per scanline for each interlaced pass.
+ ///
+ public static readonly int[] ColumnIncrement = { 8, 8, 4, 4, 2, 2, 1 };
+
+ ///
+ /// The index to start at when processing each column per scanline for each interlaced pass.
+ ///
+ public static readonly int[] FirstColumn = { 0, 4, 0, 2, 0, 1, 0 };
+
+ ///
+ /// The index to start at when processing each row per scanline for each interlaced pass.
+ ///
+ public static readonly int[] FirstRow = { 0, 0, 4, 0, 2, 0, 1 };
+
+ ///
+ /// The amount to increment when processing each row per scanline for each interlaced pass.
+ ///
+ public static readonly int[] RowIncrement = { 8, 8, 8, 4, 4, 2, 2 };
+
+ ///
+ /// Returns the correct number of columns for each interlaced pass.
+ ///
+ /// The line width.
+ /// The current pass index.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ComputeColumns(int width, int passIndex)
+ {
+ switch (passIndex)
+ {
+ case 0: return (width + 7) / 8;
+ case 1: return (width + 3) / 8;
+ case 2: return (width + 3) / 4;
+ case 3: return (width + 1) / 4;
+ case 4: return (width + 1) / 2;
+ case 5: return width / 2;
+ case 6: return width;
+ default: throw new ArgumentException($"Not a valid pass index: {passIndex}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
index ffcf9b0f3..bc5a54e8b 100644
--- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
@@ -30,7 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
// Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
int x = 1;
- for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) {
+ for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x)
+ {
ref byte scan = ref Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
scan = (byte)(scan + (above >> 1));
@@ -68,7 +69,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
resultBaseRef = 3;
int x = 0;
- for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) {
+ for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
+ {
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
@@ -77,7 +79,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
}
- for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) {
+ for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
+ {
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, xLeft);
byte above = Unsafe.Add(ref prevBaseRef, x);
@@ -97,9 +100,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
/// The above byte
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int Average(byte left, byte above)
- {
- return (left + above) >> 1;
- }
+ private static int Average(byte left, byte above) => (left + above) >> 1;
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
index 0d3df079c..4ffc39bdb 100644
--- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
@@ -72,7 +72,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
resultBaseRef = 4;
int x = 0;
- for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) {
+ for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
+ {
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
@@ -81,7 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
}
- for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) {
+ for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
+ {
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, xLeft);
byte above = Unsafe.Add(ref prevBaseRef, x);
diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
index cfb7781be..6af5f0b64 100644
--- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
@@ -59,7 +59,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
resultBaseRef = 1;
int x = 0;
- for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) {
+ for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
+ {
byte scan = Unsafe.Add(ref scanBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
@@ -67,7 +68,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += ImageMaths.FastAbs(unchecked((sbyte)res));
}
- for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) {
+ for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
+ {
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte prev = Unsafe.Add(ref scanBaseRef, xLeft);
++x;
diff --git a/src/ImageSharp/Formats/Png/PngBitDepth.cs b/src/ImageSharp/Formats/Png/PngBitDepth.cs
index 396f2c160..0321b532a 100644
--- a/src/ImageSharp/Formats/Png/PngBitDepth.cs
+++ b/src/ImageSharp/Formats/Png/PngBitDepth.cs
@@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Provides enumeration for the available PNG bit depths.
///
- public enum PngBitDepth
+ public enum PngBitDepth : byte
{
///
/// 1 bit per sample or per palette index (not per pixel).
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 1bfba68ed..3b67146b9 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -37,26 +37,6 @@ namespace SixLabors.ImageSharp.Formats.Png
[PngColorType.RgbWithAlpha] = new byte[] { 8, 16 }
};
- ///
- /// The amount to increment when processing each column per scanline for each interlaced pass
- ///
- private static readonly int[] Adam7ColumnIncrement = { 8, 8, 4, 4, 2, 2, 1 };
-
- ///
- /// The index to start at when processing each column per scanline for each interlaced pass
- ///
- private static readonly int[] Adam7FirstColumn = { 0, 4, 0, 2, 0, 1, 0 };
-
- ///
- /// The index to start at when processing each row per scanline for each interlaced pass
- ///
- private static readonly int[] Adam7FirstRow = { 0, 0, 4, 0, 2, 0, 1 };
-
- ///
- /// The amount to increment when processing each row per scanline for each interlaced pass
- ///
- private static readonly int[] Adam7RowIncrement = { 8, 8, 8, 4, 4, 2, 2 };
-
///
/// Reusable buffer for reading chunk types.
///
@@ -150,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// The index of the current scanline being processed
///
- private int currentRow = Adam7FirstRow[0];
+ private int currentRow = Adam7.FirstRow[0];
///
/// The current pass for an interlaced PNG
@@ -635,7 +615,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
while (true)
{
- int numColumns = this.ComputeColumnsAdam7(this.pass);
+ int numColumns = Adam7.ComputeColumns(this.header.Width, this.pass);
if (numColumns == 0)
{
@@ -691,11 +671,11 @@ namespace SixLabors.ImageSharp.Formats.Png
}
Span rowSpan = image.GetPixelRowSpan(this.currentRow);
- this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]);
+ this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, Adam7.FirstColumn[this.pass], Adam7.ColumnIncrement[this.pass]);
this.SwapBuffers();
- this.currentRow += Adam7RowIncrement[this.pass];
+ this.currentRow += Adam7.RowIncrement[this.pass];
}
this.pass++;
@@ -703,7 +683,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.pass < 7)
{
- this.currentRow = Adam7FirstRow[this.pass];
+ this.currentRow = Adam7.FirstRow[this.pass];
}
else
{
@@ -943,17 +923,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The containing data.
private void ReadHeaderChunk(PngMetaData pngMetaData, ReadOnlySpan data)
{
- byte bitDepth = data[8];
- this.header = new PngHeader(
- width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)),
- height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)),
- bitDepth: bitDepth,
- colorType: (PngColorType)data[9],
- compressionMethod: data[10],
- filterMethod: data[11],
- interlaceMethod: (PngInterlaceMode)data[12]);
-
- pngMetaData.BitDepth = (PngBitDepth)bitDepth;
+ this.header = PngHeader.Parse(data);
+
+ pngMetaData.BitDepth = (PngBitDepth)this.header.BitDepth;
pngMetaData.ColorType = this.header.ColorType;
}
@@ -1062,9 +1034,7 @@ namespace SixLabors.ImageSharp.Formats.Png
return true;
}
- int length = this.ReadChunkLength();
-
- if (length == -1)
+ if (!this.TryReadChunkLength(out int length))
{
chunk = default;
@@ -1078,9 +1048,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// That lets us read one byte at a time until we reach a known chunk.
this.currentStream.Position -= 3;
- length = this.ReadChunkLength();
-
- if (length == -1)
+ if (!this.TryReadChunkLength(out length))
{
chunk = default;
@@ -1180,14 +1148,9 @@ namespace SixLabors.ImageSharp.Formats.Png
///
private PngChunkType ReadChunkType()
{
- int numBytes = this.currentStream.Read(this.chunkTypeBuffer, 0, 4);
-
- if (numBytes >= 1 && numBytes <= 3)
- {
- throw new ImageFormatException("Image stream is not valid!");
- }
-
- return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.chunkTypeBuffer.AsSpan());
+ return this.currentStream.Read(this.chunkTypeBuffer, 0, 4) == 4
+ ? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.chunkTypeBuffer.AsSpan())
+ : throw new ImageFormatException("Invalid PNG data.");
}
///
@@ -1196,38 +1159,18 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Thrown if the input stream is not valid.
///
- private int ReadChunkLength()
+ private bool TryReadChunkLength(out int result)
{
- int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4);
-
- if (numBytes < 4)
+ if (this.currentStream.Read(this.chunkLengthBuffer, 0, 4) == 4)
{
- return -1;
+ result = BinaryPrimitives.ReadInt32BigEndian(this.chunkLengthBuffer);
+
+ return true;
}
- return BinaryPrimitives.ReadInt32BigEndian(this.chunkLengthBuffer);
- }
+ result = default;
- ///
- /// Returns the correct number of columns for each interlaced pass.
- ///
- /// Th current pass index
- /// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int ComputeColumnsAdam7(int passIndex)
- {
- int width = this.header.Width;
- switch (passIndex)
- {
- case 0: return (width + 7) / 8;
- case 1: return (width + 3) / 8;
- case 2: return (width + 3) / 4;
- case 3: return (width + 1) / 4;
- case 4: return (width + 1) / 2;
- case 5: return width / 2;
- case 6: return width;
- default: throw new ArgumentException($"Not a valid pass index: {passIndex}");
- }
+ return false;
}
private void SwapBuffers()
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 603162fbe..525cc8bd1 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -606,16 +606,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The .
private void WriteHeaderChunk(Stream stream, in PngHeader header)
{
- BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), header.Width);
- BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(4, 4), header.Height);
+ header.WriteTo(this.chunkDataBuffer);
- this.chunkDataBuffer[8] = header.BitDepth;
- this.chunkDataBuffer[9] = (byte)header.ColorType;
- this.chunkDataBuffer[10] = header.CompressionMethod;
- this.chunkDataBuffer[11] = header.FilterMethod;
- this.chunkDataBuffer[12] = (byte)header.InterlaceMethod;
-
- this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, 13);
+ this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, PngHeader.Size);
}
///
@@ -697,28 +690,24 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (meta.ResolutionUnits)
{
case PixelResolutionUnit.AspectRatio:
-
this.chunkDataBuffer[8] = 0;
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerInch:
-
this.chunkDataBuffer[8] = 1; // Per meter
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)));
break;
case PixelResolutionUnit.PixelsPerCentimeter:
-
this.chunkDataBuffer[8] = 1; // Per meter
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)));
break;
default:
-
this.chunkDataBuffer[8] = 1; // Per meter
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution));
@@ -782,26 +771,22 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngFilterMethod.Sub:
-
this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Up:
-
this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Average:
-
this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Paeth:
-
this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
- case PngFilterMethod.Adaptive:
+ case PngFilterMethod.Adaptive:
this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
diff --git a/src/ImageSharp/Formats/Png/PngHeader.cs b/src/ImageSharp/Formats/Png/PngHeader.cs
index df85642be..ec22f1bb4 100644
--- a/src/ImageSharp/Formats/Png/PngHeader.cs
+++ b/src/ImageSharp/Formats/Png/PngHeader.cs
@@ -1,6 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
+using System.Buffers.Binary;
+
namespace SixLabors.ImageSharp.Formats.Png
{
///
@@ -8,6 +11,8 @@ namespace SixLabors.ImageSharp.Formats.Png
///
internal readonly struct PngHeader
{
+ public const int Size = 13;
+
public PngHeader(
int width,
int height,
@@ -74,5 +79,38 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace).
///
public PngInterlaceMode InterlaceMethod { get; }
+
+ ///
+ /// Writes the header to the given buffer.
+ ///
+ /// The buffer to write to.
+ public void WriteTo(Span buffer)
+ {
+ BinaryPrimitives.WriteInt32BigEndian(buffer.Slice(0, 4), this.Width);
+ BinaryPrimitives.WriteInt32BigEndian(buffer.Slice(4, 4), this.Height);
+
+ buffer[8] = this.BitDepth;
+ buffer[9] = (byte)this.ColorType;
+ buffer[10] = this.CompressionMethod;
+ buffer[11] = this.FilterMethod;
+ buffer[12] = (byte)this.InterlaceMethod;
+ }
+
+ ///
+ /// Parses the PngHeader from the given data buffer.
+ ///
+ /// The data to parse.
+ /// The parsed PngHeader.
+ public static PngHeader Parse(ReadOnlySpan data)
+ {
+ return new PngHeader(
+ width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)),
+ height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)),
+ bitDepth: data[8],
+ colorType: (PngColorType)data[9],
+ compressionMethod: data[10],
+ filterMethod: data[11],
+ interlaceMethod: (PngInterlaceMode)data[12]);
+ }
}
}
diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
index a92220a59..583175b56 100644
--- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
@@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
///
/// Delegate to get more data once we've exhausted the current data remaining
///
- private Func getData;
+ private readonly Func getData;
///
/// Initializes a new instance of the class.