diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
index a4fbb17be..fd83f9568 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
@@ -87,8 +87,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
this.IgnoreMetadata = options.IgnoreMetadata;
this.configuration = configuration ?? Configuration.Default;
- this.HuffmanTrees = OrigHuffmanTree.CreateHuffmanTrees();
- this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.Size];
}
@@ -103,10 +101,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
///
/// Gets the huffman trees
///
- public OrigHuffmanTree[] HuffmanTrees { get; }
+ public OrigHuffmanTree[] HuffmanTrees { get; private set; }
///
- public Block8x8F[] QuantizationTables { get; }
+ public Block8x8F[] QuantizationTables { get; private set; }
///
/// Gets the temporary buffer used to store bytes read from the stream.
@@ -233,6 +231,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.InputStream = stream;
this.InputProcessor = new InputProcessor(stream, this.Temp);
+ if (!metadataOnly)
+ {
+ this.HuffmanTrees = OrigHuffmanTree.CreateHuffmanTrees();
+ this.QuantizationTables = new Block8x8F[MaxTq + 1];
+ }
+
// Check for the Start Of Image marker.
this.InputProcessor.ReadFull(this.Temp, 0, 2);
@@ -331,11 +335,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
case OrigJpegConstants.Markers.SOF1:
case OrigJpegConstants.Markers.SOF2:
this.IsProgressive = marker == OrigJpegConstants.Markers.SOF2;
- this.ProcessStartOfFrameMarker(remaining);
- if (metadataOnly && this.isJFif)
- {
- return;
- }
+ this.ProcessStartOfFrameMarker(remaining, metadataOnly);
break;
case OrigJpegConstants.Markers.DHT:
@@ -425,7 +425,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
///
private void InitDerivedMetaDataProperties()
{
- if (this.isExif)
+ if (this.isJFif)
+ {
+ this.MetaData.HorizontalResolution = this.jFif.XDensity;
+ this.MetaData.VerticalResolution = this.jFif.YDensity;
+ }
+ else if (this.isExif)
{
double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizonalTag)
? ((Rational)horizonalTag.Value).ToDouble()
@@ -441,11 +446,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.MetaData.VerticalResolution = verticalValue;
}
}
- else if (this.isJFif)
- {
- this.MetaData.HorizontalResolution = this.jFif.XDensity;
- this.MetaData.VerticalResolution = this.jFif.YDensity;
- }
}
///
@@ -634,7 +634,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// Processes the Start of Frame marker. Specified in section B.2.2.
///
/// The remaining bytes in the segment block.
- private void ProcessStartOfFrameMarker(int remaining)
+ /// Whether to parse metadata only
+ private void ProcessStartOfFrameMarker(int remaining, bool metadataOnly)
{
if (this.ComponentCount != 0)
{
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
new file mode 100644
index 000000000..0818d7309
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
@@ -0,0 +1,151 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+
+// TODO: This could be useful elsewhere.
+namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
+{
+ ///
+ /// A stream reader that add a secondary level buffer in addition to native stream buffered reading
+ /// to reduce the overhead of small incremental reads.
+ ///
+ internal class DoubleBufferedStreamReader
+ {
+ ///
+ /// The length, in bytes, of the chunk
+ ///
+ public const int ChunkLength = 4096;
+
+ private readonly Stream stream;
+
+ private readonly byte[] chunk;
+
+ private int bytesRead;
+
+ private long position;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The input stream.
+ public DoubleBufferedStreamReader(Stream stream)
+ {
+ this.stream = stream;
+ this.Length = stream.Length;
+
+ // TODO: Consider pooling this.
+ this.chunk = new byte[ChunkLength];
+ }
+
+ ///
+ /// Gets the length, in bytes, of the stream
+ ///
+ public long Length { get; }
+
+ ///
+ /// Gets or sets the current position within the stream
+ ///
+ public long Position
+ {
+ get
+ {
+ return this.position;
+ }
+
+ set
+ {
+ // Reset everything. It's easier than tracking.
+ this.position = value;
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ this.bytesRead = ChunkLength;
+ }
+ }
+
+ ///
+ /// Reads a byte from the stream and advances the position within the stream by one
+ /// byte, or returns -1 if at the end of the stream.
+ ///
+ /// The unsigned byte cast to an , or -1 if at the end of the stream.
+ public int ReadByte()
+ {
+ if (this.position >= this.Length)
+ {
+ return -1;
+ }
+
+ if (this.position == 0 || this.bytesRead >= ChunkLength)
+ {
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ this.stream.Read(this.chunk, 0, ChunkLength);
+ this.bytesRead = 0;
+ }
+
+ this.position++;
+ return this.chunk[this.bytesRead++];
+ }
+
+ ///
+ /// Skips the number of bytes in the stream
+ ///
+ /// The number of bytes to skip
+ public void Skip(int count)
+ {
+ this.position += count;
+ this.bytesRead += count;
+ }
+
+ ///
+ /// Reads a sequence of bytes from the current stream and advances the position within the stream
+ /// by the number of bytes read.
+ ///
+ ///
+ /// An array of bytes. When this method returns, the buffer contains the specified
+ /// byte array with the values between offset and (offset + count - 1) replaced by
+ /// the bytes read from the current source.
+ ///
+ ///
+ /// The zero-based byte offset in buffer at which to begin storing the data read
+ /// from the current stream.
+ ///
+ /// The maximum number of bytes to be read from the current stream.
+ ///
+ /// The total number of bytes read into the buffer. This can be less than the number
+ /// of bytes requested if that many bytes are not currently available, or zero (0)
+ /// if the end of the stream has been reached.
+ ///
+ public int Read(byte[] buffer, int offset, int count)
+ {
+ int n = 0;
+ if (buffer.Length <= ChunkLength)
+ {
+ if (this.position == 0 || count + this.bytesRead > ChunkLength)
+ {
+ // Refill our buffer then copy.
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ this.stream.Read(this.chunk, 0, ChunkLength);
+ this.bytesRead = 0;
+ }
+
+ Buffer.BlockCopy(this.chunk, this.bytesRead, buffer, offset, count);
+ this.position += count;
+ this.bytesRead += count;
+
+ n = Math.Min(count, (int)(this.Length - this.position));
+ }
+ else
+ {
+ // Read to target but don't copy to our chunk.
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ n = this.stream.Read(buffer, offset, count);
+
+ // Ensure next read fills the chunk
+ this.bytesRead = ChunkLength;
+ this.position += count;
+ }
+
+ return Math.Max(n, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs
index 8e51c0b7c..85c9f9466 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Runtime.CompilerServices;
+
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
///
@@ -13,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
///
/// The marker
/// The position within the stream
- public PdfJsFileMarker(ushort marker, long position)
+ public PdfJsFileMarker(byte marker, long position)
{
this.Marker = marker;
this.Position = position;
@@ -26,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// The marker
/// The position within the stream
/// Whether the current marker is invalid
- public PdfJsFileMarker(ushort marker, long position, bool invalid)
+ public PdfJsFileMarker(byte marker, long position, bool invalid)
{
this.Marker = marker;
this.Position = position;
@@ -36,17 +38,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
///
/// Gets a value indicating whether the current marker is invalid
///
- public bool Invalid { get; }
+ public bool Invalid
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
///
/// Gets the position of the marker within a stream
///
- public ushort Marker { get; }
+ public byte Marker
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
///
/// Gets the position of the marker within a stream
///
- public long Position { get; }
+ public long Position
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
///
public override string ToString()
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
index 5fcaa6cea..4415a681b 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
@@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// The successive approximation bit low end
public void DecodeScan(
PdfJsFrame frame,
- Stream stream,
+ DoubleBufferedStreamReader stream,
PdfJsHuffmanTables dcHuffmanTables,
PdfJsHuffmanTables acHuffmanTables,
PdfJsFrameComponent[] components,
@@ -176,7 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
int mcusPerLine,
int mcuToRead,
ref int mcu,
- Stream stream)
+ DoubleBufferedStreamReader stream)
{
if (componentsLength == 1)
{
@@ -237,7 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
int mcusPerLine,
int mcuToRead,
ref int mcu,
- Stream stream)
+ DoubleBufferedStreamReader stream)
{
if (componentsLength == 1)
{
@@ -331,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
@@ -340,7 +340,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
@@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
@@ -360,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
@@ -371,7 +371,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int mcu, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
@@ -380,7 +380,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
@@ -391,7 +391,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
@@ -400,7 +400,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
@@ -411,7 +411,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
@@ -420,7 +420,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
@@ -431,7 +431,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int ReadBit(Stream stream)
+ private int ReadBit(DoubleBufferedStreamReader stream)
{
// TODO: I wonder if we can do this two bytes at a time; libjpeg turbo seems to do that?
if (this.bitsCount > 0)
@@ -471,7 +471,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private short DecodeHuffman(ref PdfJsHuffmanTable tree, Stream stream)
+ private short DecodeHuffman(ref PdfJsHuffmanTable tree, DoubleBufferedStreamReader stream)
{
// TODO: Implement fast Huffman decoding.
// NOTES # During investigation of the libjpeg implementation it appears that they pull 32bits at a time and operate on those bits
@@ -503,7 +503,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int Receive(int length, Stream stream)
+ private int Receive(int length, DoubleBufferedStreamReader stream)
{
int n = 0;
while (length > 0)
@@ -522,7 +522,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int ReceiveAndExtend(int length, Stream stream)
+ private int ReceiveAndExtend(int length, DoubleBufferedStreamReader stream)
{
if (length == 1)
{
@@ -538,7 +538,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
return n + (-1 << length) + 1;
}
- private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, Stream stream)
+ private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{
short t = this.DecodeHuffman(ref dcHuffmanTable, stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
@@ -587,7 +587,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, Stream stream)
+ private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, DoubleBufferedStreamReader stream)
{
short t = this.DecodeHuffman(ref dcHuffmanTable, stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
@@ -600,7 +600,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, Stream stream)
+ private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, DoubleBufferedStreamReader stream)
{
int bit = this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
@@ -611,7 +611,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
Unsafe.Add(ref blockDataRef, offset) |= (short)(bit << this.successiveState);
}
- private void DecodeACFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, Stream stream)
+ private void DecodeACFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{
if (this.eobrun > 0)
{
@@ -652,7 +652,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
}
- private void DecodeACSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, Stream stream)
+ private void DecodeACSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{
int k = this.specStart;
int e = this.specEnd;
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs
index 2c369d390..437f77286 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs
@@ -22,98 +22,98 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
/// The Start of Image marker
///
- public const ushort SOI = 0xFFD8;
+ public const byte SOI = 0xD8;
///
/// The End of Image marker
///
- public const ushort EOI = 0xFFD9;
+ public const byte EOI = 0xD9;
///
/// Application specific marker for marking the jpeg format.
///
///
- public const ushort APP0 = 0xFFE0;
+ public const byte APP0 = 0xE0;
///
/// Application specific marker for marking where to store metadata.
///
- public const ushort APP1 = 0xFFE1;
+ public const byte APP1 = 0xE1;
///
/// Application specific marker for marking where to store ICC profile information.
///
- public const ushort APP2 = 0xFFE2;
+ public const byte APP2 = 0xE2;
///
/// Application specific marker.
///
- public const ushort APP3 = 0xFFE3;
+ public const byte APP3 = 0xE3;
///
/// Application specific marker.
///
- public const ushort APP4 = 0xFFE4;
+ public const byte APP4 = 0xE4;
///
/// Application specific marker.
///
- public const ushort APP5 = 0xFFE5;
+ public const byte APP5 = 0xE5;
///
/// Application specific marker.
///
- public const ushort APP6 = 0xFFE6;
+ public const byte APP6 = 0xE6;
///
/// Application specific marker.
///
- public const ushort APP7 = 0xFFE7;
+ public const byte APP7 = 0xE7;
///
/// Application specific marker.
///
- public const ushort APP8 = 0xFFE8;
+ public const byte APP8 = 0xE8;
///
/// Application specific marker.
///
- public const ushort APP9 = 0xFFE9;
+ public const byte APP9 = 0xE9;
///
/// Application specific marker.
///
- public const ushort APP10 = 0xFFEA;
+ public const byte APP10 = 0xEA;
///
/// Application specific marker.
///
- public const ushort APP11 = 0xFFEB;
+ public const byte APP11 = 0xEB;
///
/// Application specific marker.
///
- public const ushort APP12 = 0xFFEC;
+ public const byte APP12 = 0xEC;
///
/// Application specific marker.
///
- public const ushort APP13 = 0xFFED;
+ public const byte APP13 = 0xED;
///
/// Application specific marker used by Adobe for storing encoding information for DCT filters.
///
- public const ushort APP14 = 0xFFEE;
+ public const byte APP14 = 0xEE;
///
/// Application specific marker used by GraphicConverter to store JPEG quality.
///
- public const ushort APP15 = 0xFFEF;
+ public const byte APP15 = 0xEF;
///
/// The text comment marker
///
- public const ushort COM = 0xFFFE;
+ public const byte COM = 0xFE;
///
/// Define Quantization Table(s) marker
@@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more quantization tables.
///
///
- public const ushort DQT = 0xFFDB;
+ public const byte DQT = 0xDB;
///
/// Start of Frame (baseline DCT)
@@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
///
///
- public const ushort SOF0 = 0xFFC0;
+ public const byte SOF0 = 0xC0;
///
/// Start Of Frame (Extended Sequential DCT)
@@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
///
///
- public const ushort SOF1 = 0xFFC1;
+ public const byte SOF1 = 0xC1;
///
/// Start Of Frame (progressive DCT)
@@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
///
///
- public const ushort SOF2 = 0xFFC2;
+ public const byte SOF2 = 0xC2;
///
/// Define Huffman Table(s)
@@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more Huffman tables.
///
///
- public const ushort DHT = 0xFFC4;
+ public const byte DHT = 0xC4;
///
/// Define Restart Interval
@@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.
///
///
- public const ushort DRI = 0xFFDD;
+ public const byte DRI = 0xDD;
///
/// Start of Scan
@@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// will contain, and is immediately followed by entropy-coded data.
///
///
- public const ushort SOS = 0xFFDA;
+ public const byte SOS = 0xDA;
///
/// Define First Restart
@@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
///
///
- public const ushort RST0 = 0xFFD0;
+ public const byte RST0 = 0xD0;
///
/// Define Eigth Restart
@@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
///
///
- public const ushort RST7 = 0xFFD7;
+ public const byte RST7 = 0xD7;
///
/// Contains Adobe specific markers
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
index 244d97fba..1ba4826a2 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
@@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
/// Gets the input stream.
///
- public Stream InputStream { get; private set; }
+ public DoubleBufferedStreamReader InputStream { get; private set; }
///
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
@@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// The buffer to read file markers to
/// The input stream
/// The
- public static PdfJsFileMarker FindNextFileMarker(byte[] marker, Stream stream)
+ public static PdfJsFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream)
{
int value = stream.Read(marker, 0, 2);
@@ -157,7 +157,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
// According to Section B.1.1.2:
// "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF."
- while (marker[1] == PdfJsJpegConstants.Markers.Prefix)
+ int m = marker[1];
+ while (m == PdfJsJpegConstants.Markers.Prefix)
{
int suffix = stream.ReadByte();
if (suffix == -1)
@@ -165,13 +166,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
return new PdfJsFileMarker(PdfJsJpegConstants.Markers.EOI, stream.Length - 2);
}
- marker[1] = (byte)suffix;
+ m = suffix;
}
- return new PdfJsFileMarker(BinaryPrimitives.ReadUInt16BigEndian(marker), stream.Position - 2);
+ marker[1] = (byte)m;
+
+ return new PdfJsFileMarker((byte)m, stream.Position - 2);
}
- return new PdfJsFileMarker(BinaryPrimitives.ReadUInt16BigEndian(marker), stream.Position - 2, true);
+ return new PdfJsFileMarker(marker[1], stream.Position - 2, true);
}
///
@@ -194,7 +197,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
public IImageInfo Identify(Stream stream)
{
this.ParseStream(stream, true);
- this.AssignResolution();
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData);
}
@@ -206,119 +208,127 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
public void ParseStream(Stream stream, bool metadataOnly = false)
{
this.MetaData = new ImageMetaData();
- this.InputStream = stream;
+ this.InputStream = new DoubleBufferedStreamReader(stream);
// Check for the Start Of Image marker.
- var fileMarker = new PdfJsFileMarker(this.ReadUint16(), 0);
+ this.InputStream.Read(this.markerBuffer, 0, 2);
+ var fileMarker = new PdfJsFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != PdfJsJpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
}
- ushort marker = this.ReadUint16();
+ this.InputStream.Read(this.markerBuffer, 0, 2);
+ byte marker = this.markerBuffer[1];
fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2);
- this.QuantizationTables = new Block8x8F[4];
-
- // this.quantizationTables = new PdfJsQuantizationTables(this.configuration.MemoryManager);
- this.dcHuffmanTables = new PdfJsHuffmanTables();
- this.acHuffmanTables = new PdfJsHuffmanTables();
+ // Only assign what we need
+ if (!metadataOnly)
+ {
+ this.QuantizationTables = new Block8x8F[4];
+ this.dcHuffmanTables = new PdfJsHuffmanTables();
+ this.acHuffmanTables = new PdfJsHuffmanTables();
+ }
while (fileMarker.Marker != PdfJsJpegConstants.Markers.EOI)
{
- // Get the marker length
- int remaining = this.ReadUint16() - 2;
-
- switch (fileMarker.Marker)
+ if (!fileMarker.Invalid)
{
- case PdfJsJpegConstants.Markers.APP0:
- this.ProcessApplicationHeaderMarker(remaining);
- break;
+ // Get the marker length
+ int remaining = this.ReadUint16() - 2;
- case PdfJsJpegConstants.Markers.APP1:
- this.ProcessApp1Marker(remaining);
- break;
+ switch (fileMarker.Marker)
+ {
+ case PdfJsJpegConstants.Markers.SOF0:
+ case PdfJsJpegConstants.Markers.SOF1:
+ case PdfJsJpegConstants.Markers.SOF2:
- case PdfJsJpegConstants.Markers.APP2:
- this.ProcessApp2Marker(remaining);
- break;
- case PdfJsJpegConstants.Markers.APP3:
- case PdfJsJpegConstants.Markers.APP4:
- case PdfJsJpegConstants.Markers.APP5:
- case PdfJsJpegConstants.Markers.APP6:
- case PdfJsJpegConstants.Markers.APP7:
- case PdfJsJpegConstants.Markers.APP8:
- case PdfJsJpegConstants.Markers.APP9:
- case PdfJsJpegConstants.Markers.APP10:
- case PdfJsJpegConstants.Markers.APP11:
- case PdfJsJpegConstants.Markers.APP12:
- case PdfJsJpegConstants.Markers.APP13:
- this.InputStream.Skip(remaining);
- break;
+ this.ProcessStartOfFrameMarker(remaining, fileMarker, metadataOnly);
+ break;
- case PdfJsJpegConstants.Markers.APP14:
- this.ProcessApp14Marker(remaining);
- break;
+ case PdfJsJpegConstants.Markers.SOS:
+ if (!metadataOnly)
+ {
+ this.ProcessStartOfScanMarker();
+ }
- case PdfJsJpegConstants.Markers.APP15:
- case PdfJsJpegConstants.Markers.COM:
- this.InputStream.Skip(remaining);
- break;
+ break;
- case PdfJsJpegConstants.Markers.DQT:
- if (metadataOnly)
- {
- this.InputStream.Skip(remaining);
- }
- else
- {
- this.ProcessDefineQuantizationTablesMarker(remaining);
- }
+ case PdfJsJpegConstants.Markers.DHT:
+ if (metadataOnly)
+ {
+ this.InputStream.Skip(remaining);
+ }
+ else
+ {
+ this.ProcessDefineHuffmanTablesMarker(remaining);
+ }
- break;
+ break;
- case PdfJsJpegConstants.Markers.SOF0:
- case PdfJsJpegConstants.Markers.SOF1:
- case PdfJsJpegConstants.Markers.SOF2:
- this.ProcessStartOfFrameMarker(remaining, fileMarker);
- if (metadataOnly && !this.jFif.Equals(default))
- {
- this.InputStream.Skip(remaining);
- }
+ case PdfJsJpegConstants.Markers.DQT:
+ if (metadataOnly)
+ {
+ this.InputStream.Skip(remaining);
+ }
+ else
+ {
+ this.ProcessDefineQuantizationTablesMarker(remaining);
+ }
- break;
+ break;
- case PdfJsJpegConstants.Markers.DHT:
- if (metadataOnly)
- {
- this.InputStream.Skip(remaining);
- }
- else
- {
- this.ProcessDefineHuffmanTablesMarker(remaining);
- }
+ case PdfJsJpegConstants.Markers.DRI:
+ if (metadataOnly)
+ {
+ this.InputStream.Skip(remaining);
+ }
+ else
+ {
+ this.ProcessDefineRestartIntervalMarker(remaining);
+ }
- break;
+ break;
- case PdfJsJpegConstants.Markers.DRI:
- if (metadataOnly)
- {
+ case PdfJsJpegConstants.Markers.APP0:
+
+ this.ProcessApplicationHeaderMarker(remaining);
+ break;
+
+ case PdfJsJpegConstants.Markers.APP1:
+
+ this.ProcessApp1Marker(remaining);
+ break;
+
+ case PdfJsJpegConstants.Markers.APP2:
+
+ this.ProcessApp2Marker(remaining);
+ break;
+
+ case PdfJsJpegConstants.Markers.APP3:
+ case PdfJsJpegConstants.Markers.APP4:
+ case PdfJsJpegConstants.Markers.APP5:
+ case PdfJsJpegConstants.Markers.APP6:
+ case PdfJsJpegConstants.Markers.APP7:
+ case PdfJsJpegConstants.Markers.APP8:
+ case PdfJsJpegConstants.Markers.APP9:
+ case PdfJsJpegConstants.Markers.APP10:
+ case PdfJsJpegConstants.Markers.APP11:
+ case PdfJsJpegConstants.Markers.APP12:
+ case PdfJsJpegConstants.Markers.APP13:
this.InputStream.Skip(remaining);
- }
- else
- {
- this.ProcessDefineRestartIntervalMarker(remaining);
- }
+ break;
- break;
+ case PdfJsJpegConstants.Markers.APP14:
- case PdfJsJpegConstants.Markers.SOS:
- if (!metadataOnly)
- {
- this.ProcessStartOfScanMarker();
- }
+ this.ProcessApp14Marker(remaining);
+ break;
- break;
+ case PdfJsJpegConstants.Markers.APP15:
+ case PdfJsJpegConstants.Markers.COM:
+ this.InputStream.Skip(remaining);
+ break;
+ }
}
// Read on.
@@ -328,6 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.ImageWidth = this.Frame.SamplesPerLine;
this.ImageHeight = this.Frame.Scanlines;
this.ComponentCount = this.Frame.ComponentCount;
+ this.AssignResolution();
}
///
@@ -379,7 +390,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
private void AssignResolution()
{
- if (this.isExif)
+ if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0)
+ {
+ this.MetaData.HorizontalResolution = this.jFif.XDensity;
+ this.MetaData.VerticalResolution = this.jFif.YDensity;
+ }
+ else if (this.isExif)
{
double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizontalTag)
? ((Rational)horizontalTag.Value).ToDouble()
@@ -395,11 +411,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.MetaData.VerticalResolution = verticalValue;
}
}
- else if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0)
- {
- this.MetaData.HorizontalResolution = this.jFif.XDensity;
- this.MetaData.VerticalResolution = this.jFif.YDensity;
- }
}
///
@@ -593,7 +604,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
/// The remaining bytes in the segment block.
/// The current frame marker.
- private void ProcessStartOfFrameMarker(int remaining, PdfJsFileMarker frameMarker)
+ /// Whether to parse metadata only
+ private void ProcessStartOfFrameMarker(int remaining, PdfJsFileMarker frameMarker, bool metadataOnly)
{
if (this.Frame != null)
{
@@ -622,11 +634,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
int maxV = 0;
int index = 6;
- // No need to pool this. They max out at 4
- this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
- this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount];
+ if (!metadataOnly)
+ {
+ // No need to pool this. They max out at 4
+ this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
+ this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount];
+ }
- for (int i = 0; i < this.Frame.Components.Length; i++)
+ for (int i = 0; i < this.Frame.ComponentCount; i++)
{
byte hv = this.temp[index + 1];
int h = hv >> 4;
@@ -642,17 +657,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
maxV = v;
}
- var component = new PdfJsFrameComponent(this.configuration.MemoryManager, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
+ if (!metadataOnly)
+ {
+ var component = new PdfJsFrameComponent(this.configuration.MemoryManager, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
- this.Frame.Components[i] = component;
- this.Frame.ComponentIds[i] = component.Id;
+ this.Frame.Components[i] = component;
+ this.Frame.ComponentIds[i] = component.Id;
+ }
index += 3;
}
this.Frame.MaxHorizontalFactor = maxH;
this.Frame.MaxVerticalFactor = maxV;
- this.Frame.InitComponents();
+
+ if (!metadataOnly)
+ {
+ this.Frame.InitComponents();
+ }
}
///
@@ -799,7 +821,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel
{
this.ColorSpace = this.DeduceJpegColorSpace();
- this.AssignResolution();
using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this))
{
var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData);
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/Identify.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/Identify.cs
new file mode 100644
index 000000000..79e9e9e76
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/Identify.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
+using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
+using SixLabors.ImageSharp.Tests;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
+{
+ [Config(typeof(Config.ShortClr))]
+ public class Identify
+ {
+ private byte[] jpegBytes;
+
+ private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
+
+ [Params(TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Calliphora)]
+ public string TestImage { get; set; }
+
+ [GlobalSetup]
+ public void ReadImages()
+ {
+ if (this.jpegBytes == null)
+ {
+ this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath);
+ }
+ }
+
+ [Benchmark]
+ public IImageInfo IdentifyGolang()
+ {
+ using (var memoryStream = new MemoryStream(this.jpegBytes))
+ {
+ var decoder = new OrigJpegDecoder();
+
+ return decoder.Identify(Configuration.Default, memoryStream);
+ }
+ }
+
+ [Benchmark]
+ public IImageInfo IdentifyPdfJs()
+ {
+ using (var memoryStream = new MemoryStream(this.jpegBytes))
+ {
+ var decoder = new PdfJsJpegDecoder();
+ return decoder.Identify(Configuration.Default, memoryStream);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/General/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/General/DoubleBufferedStreams.cs
new file mode 100644
index 000000000..665d0cbad
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/General/DoubleBufferedStreams.cs
@@ -0,0 +1,53 @@
+using System;
+using System.IO;
+using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
+
+namespace SixLabors.ImageSharp.Benchmarks.General
+{
+ [Config(typeof(Config.ShortClr))]
+ public class DoubleBufferedStreams
+ {
+ private byte[] buffer = CreateTestBytes();
+
+ [Benchmark]
+ public int StandardStream()
+ {
+ int r = 0;
+ using (var stream = new MemoryStream(this.buffer))
+ {
+ for (int i = 0; i < stream.Length; i++)
+ {
+ r += stream.ReadByte();
+ }
+ }
+
+ return r;
+ }
+
+ [Benchmark]
+ public int ChunkedStream()
+ {
+ int r = 0;
+ using (var stream = new MemoryStream(this.buffer))
+ {
+ var reader = new DoubleBufferedStreamReader(stream);
+ for (int i = 0; i < reader.Length; i++)
+ {
+ r += reader.ReadByte();
+ }
+ }
+
+ return r;
+ }
+
+ private static byte[] CreateTestBytes()
+ {
+ byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3];
+ var random = new Random();
+ random.NextBytes(buffer);
+
+ return buffer;
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs
new file mode 100644
index 000000000..c1049f025
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs
@@ -0,0 +1,132 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Jpg
+{
+ public class DoubleBufferedStreamReaderTests
+ {
+ [Fact]
+ public void DoubleBufferedStreamReaderCanReadSingleByteFromOrigin()
+ {
+ using (MemoryStream stream = CreateTestStream())
+ {
+ byte[] expected = stream.ToArray();
+ var reader = new DoubleBufferedStreamReader(stream);
+
+ Assert.Equal(expected[0], reader.ReadByte());
+
+ // We've read a whole chunk but increment by 1 in our reader.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
+ Assert.Equal(1, reader.Position);
+ }
+ }
+
+ [Fact]
+ public void DoubleBufferedStreamReaderCanReadSubsequentSingleByteCorrectly()
+ {
+ using (MemoryStream stream = CreateTestStream())
+ {
+ byte[] expected = stream.ToArray();
+ var reader = new DoubleBufferedStreamReader(stream);
+
+ for (int i = 0; i < expected.Length; i++)
+ {
+ Assert.Equal(expected[i], reader.ReadByte());
+ Assert.Equal(i + 1, reader.Position);
+
+ if (i < DoubleBufferedStreamReader.ChunkLength)
+ {
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
+ }
+ else if (i >= DoubleBufferedStreamReader.ChunkLength && i < DoubleBufferedStreamReader.ChunkLength * 2)
+ {
+ // We should have advanced to the second chunk now.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2);
+ }
+ else
+ {
+ // We should have advanced to the third chunk now.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void DoubleBufferedStreamReaderCanReadMultipleBytesFromOrigin()
+ {
+ using (MemoryStream stream = CreateTestStream())
+ {
+ byte[] buffer = new byte[2];
+ byte[] expected = stream.ToArray();
+ var reader = new DoubleBufferedStreamReader(stream);
+
+ Assert.Equal(2, reader.Read(buffer, 0, 2));
+ Assert.Equal(expected[0], buffer[0]);
+ Assert.Equal(expected[1], buffer[1]);
+
+ // We've read a whole chunk but increment by the buffer length in our reader.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
+ Assert.Equal(buffer.Length, reader.Position);
+ }
+ }
+
+ [Fact]
+ public void DoubleBufferedStreamReaderCanReadSubsequentMultipleByteCorrectly()
+ {
+ using (MemoryStream stream = CreateTestStream())
+ {
+ byte[] buffer = new byte[2];
+ byte[] expected = stream.ToArray();
+ var reader = new DoubleBufferedStreamReader(stream);
+
+ for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 2)
+ {
+ if (o + 2 == expected.Length)
+ {
+ // We've reached the end of the stream
+ Assert.Equal(0, reader.Read(buffer, 0, 2));
+ }
+ else
+ {
+ Assert.Equal(2, reader.Read(buffer, 0, 2));
+ }
+
+ Assert.Equal(expected[o], buffer[0]);
+ Assert.Equal(expected[o + 1], buffer[1]);
+ Assert.Equal(o + 2, reader.Position);
+
+ int offset = i * 2;
+ if (offset < DoubleBufferedStreamReader.ChunkLength)
+ {
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
+ }
+ else if (offset >= DoubleBufferedStreamReader.ChunkLength && offset < DoubleBufferedStreamReader.ChunkLength * 2)
+ {
+ // We should have advanced to the second chunk now.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2);
+ }
+ else
+ {
+ // We should have advanced to the third chunk now.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3);
+ }
+ }
+ }
+ }
+
+ private MemoryStream CreateTestStream()
+ {
+ byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3];
+ var random = new Random();
+ random.NextBytes(buffer);
+
+ return new MemoryStream(buffer);
+ }
+ }
+}