mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
13 KiB
368 lines
13 KiB
// <copyright file="BufferProcessor.cs" company="James Jackson-South">
|
|
// Copyright (c) James Jackson-South and contributors.
|
|
// Licensed under the Apache License, Version 2.0.
|
|
// </copyright>
|
|
|
|
namespace ImageSharp.Formats.Jpg
|
|
{
|
|
using System;
|
|
using System.IO;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
/// <summary>
|
|
/// Encapsulates stream reading and processing data and operations for <see cref="JpegDecoderCore"/>.
|
|
/// It's a value type for imporved data locality, and reduced number of CALLVIRT-s
|
|
/// </summary>
|
|
internal struct BufferProcessor : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Holds the unprocessed bits that have been taken from the byte-stream.
|
|
/// </summary>
|
|
public Bits Bits;
|
|
|
|
/// <summary>
|
|
/// The byte buffer
|
|
/// </summary>
|
|
public Bytes Bytes;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="BufferProcessor"/> struct.
|
|
/// </summary>
|
|
/// <param name="inputStream">The input <see cref="Stream"/></param>
|
|
/// <param name="temp">Temporal buffer, same as <see cref="JpegDecoderCore.Temp"/></param>
|
|
public BufferProcessor(Stream inputStream, byte[] temp)
|
|
{
|
|
this.Bits = default(Bits);
|
|
this.Bytes = Bytes.Create();
|
|
this.InputStream = inputStream;
|
|
this.Temp = temp;
|
|
this.UnexpectedEndOfStreamReached = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the input stream
|
|
/// </summary>
|
|
public Stream InputStream { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the temporal buffer, same instance as <see cref="JpegDecoderCore.Temp"/>
|
|
/// </summary>
|
|
public byte[] Temp { get; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether an unexpected EOF reached in <see cref="InputStream"/>.
|
|
/// </summary>
|
|
public bool UnexpectedEndOfStreamReached { get; set; }
|
|
|
|
/// <summary>
|
|
/// If errorCode indicates unexpected EOF, sets <see cref="UnexpectedEndOfStreamReached"/> to true and returns false.
|
|
/// Calls <see cref="DecoderThrowHelper.EnsureNoError"/> and returns true otherwise.
|
|
/// </summary>
|
|
/// <param name="errorCode">The <see cref="DecoderErrorCode"/></param>
|
|
/// <returns><see cref="bool"/> indicating whether everything is OK</returns>
|
|
public bool CheckEOFEnsureNoError(DecoderErrorCode errorCode)
|
|
{
|
|
if (errorCode == DecoderErrorCode.UnexpectedEndOfStream)
|
|
{
|
|
this.UnexpectedEndOfStreamReached = true;
|
|
return false;
|
|
}
|
|
|
|
errorCode.EnsureNoError();
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If errorCode indicates unexpected EOF, sets <see cref="UnexpectedEndOfStreamReached"/> to true and returns false.
|
|
/// Returns true otherwise.
|
|
/// </summary>
|
|
/// <param name="errorCode">The <see cref="DecoderErrorCode"/></param>
|
|
/// <returns><see cref="bool"/> indicating whether everything is OK</returns>
|
|
public bool CheckEOF(DecoderErrorCode errorCode)
|
|
{
|
|
if (errorCode == DecoderErrorCode.UnexpectedEndOfStream)
|
|
{
|
|
this.UnexpectedEndOfStreamReached = true;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
this.Bytes.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
|
|
/// </summary>
|
|
/// <returns>The <see cref="byte" /></returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public byte ReadByte()
|
|
{
|
|
return this.Bytes.ReadByte(this.InputStream);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decodes a single bit
|
|
/// TODO: This method (and also the usages) could be optimized by batching!
|
|
/// </summary>
|
|
/// <param name="result">The decoded bit as a <see cref="bool"/></param>
|
|
/// <returns>The <see cref="DecoderErrorCode" /></returns>
|
|
public DecoderErrorCode DecodeBitUnsafe(out bool result)
|
|
{
|
|
if (this.Bits.UnreadBits == 0)
|
|
{
|
|
DecoderErrorCode errorCode = this.Bits.Ensure1BitUnsafe(ref this);
|
|
if (errorCode != DecoderErrorCode.NoError)
|
|
{
|
|
result = false;
|
|
return errorCode;
|
|
}
|
|
}
|
|
|
|
result = (this.Bits.Accumulator & this.Bits.Mask) != 0;
|
|
this.Bits.UnreadBits--;
|
|
this.Bits.Mask >>= 1;
|
|
return DecoderErrorCode.NoError;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads exactly length bytes into data. It does not care about byte stuffing.
|
|
/// Does not throw on errors, returns <see cref="JpegDecoderCore"/> instead!
|
|
/// </summary>
|
|
/// <param name="data">The data to write to.</param>
|
|
/// <param name="offset">The offset in the source buffer</param>
|
|
/// <param name="length">The number of bytes to read</param>
|
|
/// <returns>The <see cref="DecoderErrorCode"/></returns>
|
|
public DecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length)
|
|
{
|
|
// Unread the overshot bytes, if any.
|
|
if (this.Bytes.UnreadableBytes != 0)
|
|
{
|
|
if (this.Bits.UnreadBits >= 8)
|
|
{
|
|
this.UnreadByteStuffedByte();
|
|
}
|
|
|
|
this.Bytes.UnreadableBytes = 0;
|
|
}
|
|
|
|
DecoderErrorCode errorCode = DecoderErrorCode.NoError;
|
|
while (length > 0)
|
|
{
|
|
if (this.Bytes.J - this.Bytes.I >= length)
|
|
{
|
|
Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, length);
|
|
this.Bytes.I += length;
|
|
length -= length;
|
|
}
|
|
else
|
|
{
|
|
Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, this.Bytes.J - this.Bytes.I);
|
|
offset += this.Bytes.J - this.Bytes.I;
|
|
length -= this.Bytes.J - this.Bytes.I;
|
|
this.Bytes.I += this.Bytes.J - this.Bytes.I;
|
|
|
|
errorCode = this.Bytes.FillUnsafe(this.InputStream);
|
|
}
|
|
}
|
|
|
|
return errorCode;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decodes the given number of bits
|
|
/// </summary>
|
|
/// <param name="count">The number of bits to decode.</param>
|
|
/// <param name="result">The <see cref="uint" /> result</param>
|
|
/// <returns>The <see cref="DecoderErrorCode"/></returns>
|
|
public DecoderErrorCode DecodeBitsUnsafe(int count, out int result)
|
|
{
|
|
if (this.Bits.UnreadBits < count)
|
|
{
|
|
this.Bits.EnsureNBits(count, ref this);
|
|
}
|
|
|
|
result = this.Bits.Accumulator >> (this.Bits.UnreadBits - count);
|
|
result = result & ((1 << count) - 1);
|
|
this.Bits.UnreadBits -= count;
|
|
this.Bits.Mask >>= count;
|
|
return DecoderErrorCode.NoError;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts the next Huffman-coded value from the bit-stream into result, decoded according to the given value.
|
|
/// </summary>
|
|
/// <param name="huffmanTree">The huffman value</param>
|
|
/// <param name="result">The decoded <see cref="byte" /></param>
|
|
/// <returns>The <see cref="DecoderErrorCode"/></returns>
|
|
public DecoderErrorCode DecodeHuffmanUnsafe(ref HuffmanTree huffmanTree, out int result)
|
|
{
|
|
result = 0;
|
|
|
|
if (huffmanTree.Length == 0)
|
|
{
|
|
DecoderThrowHelper.ThrowImageFormatException.UninitializedHuffmanTable();
|
|
}
|
|
|
|
if (this.Bits.UnreadBits < 8)
|
|
{
|
|
DecoderErrorCode errorCode = this.Bits.Ensure8BitsUnsafe(ref this);
|
|
|
|
if (errorCode == DecoderErrorCode.NoError)
|
|
{
|
|
int lutIndex = (this.Bits.Accumulator >> (this.Bits.UnreadBits - HuffmanTree.LutSizeLog2)) & 0xFF;
|
|
int v = huffmanTree.Lut[lutIndex];
|
|
|
|
if (v != 0)
|
|
{
|
|
int n = (v & 0xFF) - 1;
|
|
this.Bits.UnreadBits -= n;
|
|
this.Bits.Mask >>= n;
|
|
result = v >> 8;
|
|
return errorCode;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.UnreadByteStuffedByte();
|
|
return errorCode;
|
|
}
|
|
}
|
|
|
|
int code = 0;
|
|
for (int i = 0; i < HuffmanTree.MaxCodeLength; i++)
|
|
{
|
|
if (this.Bits.UnreadBits == 0)
|
|
{
|
|
this.Bits.EnsureNBits(1, ref this);
|
|
}
|
|
|
|
if ((this.Bits.Accumulator & this.Bits.Mask) != 0)
|
|
{
|
|
code |= 1;
|
|
}
|
|
|
|
this.Bits.UnreadBits--;
|
|
this.Bits.Mask >>= 1;
|
|
|
|
if (code <= huffmanTree.MaxCodes[i])
|
|
{
|
|
result = huffmanTree.GetValue(code, i);
|
|
return DecoderErrorCode.NoError;
|
|
}
|
|
|
|
code <<= 1;
|
|
}
|
|
|
|
// Unrecoverable error, throwing:
|
|
DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode();
|
|
|
|
// DUMMY RETURN! C# doesn't know we have thrown an exception!
|
|
return DecoderErrorCode.NoError;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Skips the next n bytes.
|
|
/// </summary>
|
|
/// <param name="count">The number of bytes to ignore.</param>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Skip(int count)
|
|
{
|
|
DecoderErrorCode errorCode = this.SkipUnsafe(count);
|
|
errorCode.EnsureNoError();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Skips the next n bytes.
|
|
/// Does not throw, returns <see cref="DecoderErrorCode"/> instead!
|
|
/// </summary>
|
|
/// <param name="count">The number of bytes to ignore.</param>
|
|
/// <returns>The <see cref="DecoderErrorCode"/></returns>
|
|
public DecoderErrorCode SkipUnsafe(int count)
|
|
{
|
|
// Unread the overshot bytes, if any.
|
|
if (this.Bytes.UnreadableBytes != 0)
|
|
{
|
|
if (this.Bits.UnreadBits >= 8)
|
|
{
|
|
this.UnreadByteStuffedByte();
|
|
}
|
|
|
|
this.Bytes.UnreadableBytes = 0;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
int m = this.Bytes.J - this.Bytes.I;
|
|
if (m > count)
|
|
{
|
|
m = count;
|
|
}
|
|
|
|
this.Bytes.I += m;
|
|
count -= m;
|
|
if (count == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
DecoderErrorCode errorCode = this.Bytes.FillUnsafe(this.InputStream);
|
|
if (errorCode != DecoderErrorCode.NoError)
|
|
{
|
|
return errorCode;
|
|
}
|
|
}
|
|
|
|
return DecoderErrorCode.NoError;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads exactly length bytes into data. It does not care about byte stuffing.
|
|
/// </summary>
|
|
/// <param name="data">The data to write to.</param>
|
|
/// <param name="offset">The offset in the source buffer</param>
|
|
/// <param name="length">The number of bytes to read</param>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void ReadFull(byte[] data, int offset, int length)
|
|
{
|
|
DecoderErrorCode errorCode = this.ReadFullUnsafe(data, offset, length);
|
|
errorCode.EnsureNoError();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Undoes the most recent ReadByteStuffedByte call,
|
|
/// giving a byte of data back from bits to bytes. The Huffman look-up table
|
|
/// requires at least 8 bits for look-up, which means that Huffman decoding can
|
|
/// sometimes overshoot and read one or two too many bytes. Two-byte overshoot
|
|
/// can happen when expecting to read a 0xff 0x00 byte-stuffed byte.
|
|
/// </summary>
|
|
public void UnreadByteStuffedByte()
|
|
{
|
|
this.Bytes.I -= this.Bytes.UnreadableBytes;
|
|
this.Bytes.UnreadableBytes = 0;
|
|
if (this.Bits.UnreadBits >= 8)
|
|
{
|
|
this.Bits.Accumulator >>= 8;
|
|
this.Bits.UnreadBits -= 8;
|
|
this.Bits.Mask >>= 8;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Receive extend
|
|
/// </summary>
|
|
/// <param name="t">Byte</param>
|
|
/// <param name="x">Read bits value</param>
|
|
/// <returns>The <see cref="DecoderErrorCode"/></returns>
|
|
public DecoderErrorCode ReceiveExtendUnsafe(int t, out int x)
|
|
{
|
|
return this.Bits.ReceiveExtendUnsafe(t, ref this, out x);
|
|
}
|
|
}
|
|
}
|