Browse Source

Increase Identify performance and reduce allocations

af/merge-core
James Jackson-South 8 years ago
parent
commit
bccff8beb4
  1. 33
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  2. 151
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
  3. 24
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs
  4. 44
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
  5. 56
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs
  6. 237
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  7. 52
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/Identify.cs
  8. 53
      tests/ImageSharp.Benchmarks/General/DoubleBufferedStreams.cs
  9. 132
      tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs

33
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
/// <summary>
/// Gets the huffman trees
/// </summary>
public OrigHuffmanTree[] HuffmanTrees { get; }
public OrigHuffmanTree[] HuffmanTrees { get; private set; }
/// <inheritdoc />
public Block8x8F[] QuantizationTables { get; }
public Block8x8F[] QuantizationTables { get; private set; }
/// <summary>
/// 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
/// </summary>
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;
}
}
/// <summary>
@ -634,7 +634,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// Processes the Start of Frame marker. Specified in section B.2.2.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessStartOfFrameMarker(int remaining)
/// <param name="metadataOnly">Whether to parse metadata only</param>
private void ProcessStartOfFrameMarker(int remaining, bool metadataOnly)
{
if (this.ComponentCount != 0)
{

151
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
{
/// <summary>
/// A stream reader that add a secondary level buffer in addition to native stream buffered reading
/// to reduce the overhead of small incremental reads.
/// </summary>
internal class DoubleBufferedStreamReader
{
/// <summary>
/// The length, in bytes, of the chunk
/// </summary>
public const int ChunkLength = 4096;
private readonly Stream stream;
private readonly byte[] chunk;
private int bytesRead;
private long position;
/// <summary>
/// Initializes a new instance of the <see cref="DoubleBufferedStreamReader"/> class.
/// </summary>
/// <param name="stream">The input stream.</param>
public DoubleBufferedStreamReader(Stream stream)
{
this.stream = stream;
this.Length = stream.Length;
// TODO: Consider pooling this.
this.chunk = new byte[ChunkLength];
}
/// <summary>
/// Gets the length, in bytes, of the stream
/// </summary>
public long Length { get; }
/// <summary>
/// Gets or sets the current position within the stream
/// </summary>
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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The unsigned byte cast to an <see cref="int"/>, or -1 if at the end of the stream.</returns>
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++];
}
/// <summary>
/// Skips the number of bytes in the stream
/// </summary>
/// <param name="count">The number of bytes to skip</param>
public void Skip(int count)
{
this.position += count;
this.bytesRead += count;
}
/// <summary>
/// Reads a sequence of bytes from the current stream and advances the position within the stream
/// by the number of bytes read.
/// </summary>
/// <param name="buffer">
/// 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.
/// </param>
/// <param name="offset">
/// The zero-based byte offset in buffer at which to begin storing the data read
/// from the current stream.
/// </param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// 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.
/// </returns>
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);
}
}
}

24
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
{
/// <summary>
@ -13,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
/// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param>
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
/// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param>
/// <param name="invalid">Whether the current marker is invalid</param>
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
/// <summary>
/// Gets a value indicating whether the current marker is invalid
/// </summary>
public bool Invalid { get; }
public bool Invalid
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Gets the position of the marker within a stream
/// </summary>
public ushort Marker { get; }
public byte Marker
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Gets the position of the marker within a stream
/// </summary>
public long Position { get; }
public long Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <inheritdoc/>
public override string ToString()

44
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs

@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="successive">The successive approximation bit low end</param>
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;

56
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs

@ -22,98 +22,98 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// The Start of Image marker
/// </summary>
public const ushort SOI = 0xFFD8;
public const byte SOI = 0xD8;
/// <summary>
/// The End of Image marker
/// </summary>
public const ushort EOI = 0xFFD9;
public const byte EOI = 0xD9;
/// <summary>
/// Application specific marker for marking the jpeg format.
/// <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html"/>
/// </summary>
public const ushort APP0 = 0xFFE0;
public const byte APP0 = 0xE0;
/// <summary>
/// Application specific marker for marking where to store metadata.
/// </summary>
public const ushort APP1 = 0xFFE1;
public const byte APP1 = 0xE1;
/// <summary>
/// Application specific marker for marking where to store ICC profile information.
/// </summary>
public const ushort APP2 = 0xFFE2;
public const byte APP2 = 0xE2;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP3 = 0xFFE3;
public const byte APP3 = 0xE3;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP4 = 0xFFE4;
public const byte APP4 = 0xE4;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP5 = 0xFFE5;
public const byte APP5 = 0xE5;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP6 = 0xFFE6;
public const byte APP6 = 0xE6;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP7 = 0xFFE7;
public const byte APP7 = 0xE7;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP8 = 0xFFE8;
public const byte APP8 = 0xE8;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP9 = 0xFFE9;
public const byte APP9 = 0xE9;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP10 = 0xFFEA;
public const byte APP10 = 0xEA;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP11 = 0xFFEB;
public const byte APP11 = 0xEB;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP12 = 0xFFEC;
public const byte APP12 = 0xEC;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP13 = 0xFFED;
public const byte APP13 = 0xED;
/// <summary>
/// Application specific marker used by Adobe for storing encoding information for DCT filters.
/// </summary>
public const ushort APP14 = 0xFFEE;
public const byte APP14 = 0xEE;
/// <summary>
/// Application specific marker used by GraphicConverter to store JPEG quality.
/// </summary>
public const ushort APP15 = 0xFFEF;
public const byte APP15 = 0xEF;
/// <summary>
/// The text comment marker
/// </summary>
public const ushort COM = 0xFFFE;
public const byte COM = 0xFE;
/// <summary>
/// Define Quantization Table(s) marker
@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more quantization tables.
/// </remarks>
/// </summary>
public const ushort DQT = 0xFFDB;
public const byte DQT = 0xDB;
/// <summary>
/// Start of Frame (baseline DCT)
@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
/// </remarks>
/// </summary>
public const ushort SOF0 = 0xFFC0;
public const byte SOF0 = 0xC0;
/// <summary>
/// Start Of Frame (Extended Sequential DCT)
@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
/// </remarks>
/// </summary>
public const ushort SOF1 = 0xFFC1;
public const byte SOF1 = 0xC1;
/// <summary>
/// Start Of Frame (progressive DCT)
@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
/// </remarks>
/// </summary>
public const ushort SOF2 = 0xFFC2;
public const byte SOF2 = 0xC2;
/// <summary>
/// Define Huffman Table(s)
@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more Huffman tables.
/// </remarks>
/// </summary>
public const ushort DHT = 0xFFC4;
public const byte DHT = 0xC4;
/// <summary>
/// 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.
/// </remarks>
/// </summary>
public const ushort DRI = 0xFFDD;
public const byte DRI = 0xDD;
/// <summary>
/// Start of Scan
@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// will contain, and is immediately followed by entropy-coded data.
/// </remarks>
/// </summary>
public const ushort SOS = 0xFFDA;
public const byte SOS = 0xDA;
/// <summary>
/// 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.
/// </remarks>
/// </summary>
public const ushort RST0 = 0xFFD0;
public const byte RST0 = 0xD0;
/// <summary>
/// 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.
/// </remarks>
/// </summary>
public const ushort RST7 = 0xFFD7;
public const byte RST7 = 0xD7;
/// <summary>
/// Contains Adobe specific markers

237
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs

@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// Gets the input stream.
/// </summary>
public Stream InputStream { get; private set; }
public DoubleBufferedStreamReader InputStream { get; private set; }
/// <summary>
/// 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
/// <param name="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param>
/// <returns>The <see cref="PdfJsFileMarker"/></returns>
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);
}
/// <summary>
@ -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();
}
/// <inheritdoc/>
@ -379,7 +390,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary>
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;
}
}
/// <summary>
@ -593,7 +604,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</param>
private void ProcessStartOfFrameMarker(int remaining, PdfJsFileMarker frameMarker)
/// <param name="metadataOnly">Whether to parse metadata only</param>
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();
}
}
/// <summary>
@ -799,7 +821,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel<TPixel>
{
this.ColorSpace = this.DeduceJpegColorSpace();
this.AssignResolution();
using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this))
{
var image = new Image<TPixel>(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData);

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

53
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;
}
}
}

132
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);
}
}
}
Loading…
Cancel
Save