//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
namespace ImageSharp.Formats.Jpg
{
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
///
/// Bytes is a byte buffer, similar to a stream, except that it
/// has to be able to unread more than 1 byte, due to byte stuffing.
/// Byte stuffing is specified in section F.1.2.3.
///
internal struct Bytes : IDisposable
{
///
/// Gets or sets the buffer.
/// buffer[i:j] are the buffered bytes read from the underlying
/// stream that haven't yet been passed further on.
///
public byte[] Buffer;
///
/// Start of bytes read
///
public int I;
///
/// End of bytes read
///
public int J;
///
/// Gets or sets the unreadable bytes. The number of bytes to back up i after
/// overshooting. It can be 0, 1 or 2.
///
public int UnreadableBytes;
private static readonly ArrayPool ArrayPool = ArrayPool.Create(4096, 50);
///
/// Creates a new instance of the , and initializes it's buffer.
///
/// The bytes created
public static Bytes Create()
{
return new Bytes { Buffer = ArrayPool.Rent(4096) };
}
///
/// Disposes of the underlying buffer
///
public void Dispose()
{
if (this.Buffer != null)
{
ArrayPool.Return(this.Buffer);
}
this.Buffer = null;
}
///
/// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data.
///
/// Input stream
/// The result
/// The
public DecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out byte x)
{
// Take the fast path if bytes.buf contains at least two bytes.
if (this.I + 2 <= this.J)
{
x = this.Buffer[this.I];
this.I++;
this.UnreadableBytes = 1;
if (x != JpegConstants.Markers.XFF)
{
return DecoderErrorCode.NoError;
}
if (this.Buffer[this.I] != 0x00)
{
return DecoderErrorCode.MissingFF00;
}
this.I++;
this.UnreadableBytes = 2;
x = JpegConstants.Markers.XFF;
return DecoderErrorCode.NoError;
}
this.UnreadableBytes = 0;
DecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out x);
this.UnreadableBytes = 1;
if (errorCode != DecoderErrorCode.NoError)
{
return errorCode;
}
if (x != JpegConstants.Markers.XFF)
{
return DecoderErrorCode.NoError;
}
errorCode = this.ReadByteUnsafe(inputStream, out x);
this.UnreadableBytes = 2;
if (errorCode != DecoderErrorCode.NoError)
{
return errorCode;
}
if (x != 0x00)
{
return DecoderErrorCode.MissingFF00;
}
x = JpegConstants.Markers.XFF;
return DecoderErrorCode.NoError;
}
///
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
///
/// Input stream
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte(Stream inputStream)
{
byte result;
DecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out result);
errorCode.EnsureNoError();
return result;
}
///
/// Extracts the next byte, whether buffered or not buffered into the result out parameter. It does not care about byte stuffing.
/// This method does not throw on format error, it returns a instead.
///
/// Input stream
/// The result as out parameter
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result)
{
DecoderErrorCode errorCode = DecoderErrorCode.NoError;
while (this.I == this.J)
{
errorCode = this.FillUnsafe(inputStream);
if (errorCode != DecoderErrorCode.NoError)
{
result = 0;
return errorCode;
}
}
result = this.Buffer[this.I];
this.I++;
this.UnreadableBytes = 0;
return errorCode;
}
///
/// Fills up the bytes buffer from the underlying stream.
/// It should only be called when there are no unread bytes in bytes.
///
/// Thrown when reached end of stream unexpectedly.
/// Input stream
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Fill(Stream inputStream)
{
DecoderErrorCode errorCode = this.FillUnsafe(inputStream);
errorCode.EnsureNoError();
}
///
/// Fills up the bytes buffer from the underlying stream.
/// It should only be called when there are no unread bytes in bytes.
/// This method does not throw , returns a instead!
///
/// Input stream
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DecoderErrorCode FillUnsafe(Stream inputStream)
{
if (this.I != this.J)
{
// Unrecoverable error in the input, throwing!
DecoderThrowHelper.ThrowImageFormatException.FillCalledWhenUnreadBytesExist();
}
// Move the last 2 bytes to the start of the buffer, in case we need
// to call UnreadByteStuffedByte.
if (this.J > 2)
{
this.Buffer[0] = this.Buffer[this.J - 2];
this.Buffer[1] = this.Buffer[this.J - 1];
this.I = 2;
this.J = 2;
}
// Fill in the rest of the buffer.
int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J);
if (n == 0)
{
return DecoderErrorCode.UnexpectedEndOfStream;
}
this.J += n;
return DecoderErrorCode.NoError;
}
}
}