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); + } + } +}