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