/// <summary> /// Reads and returns an application extension from the supplied input /// stream. /// </summary> /// <param name="inputStream"> /// The input stream to read. /// </param> public ApplicationExtension(Stream inputStream) { DataBlock identificationBlock = new DataBlock(inputStream); Collection <DataBlock> applicationData = new Collection <DataBlock>(); if (!identificationBlock.TestState(ErrorState.EndOfInputStream)) { // Read application specific data DataBlock thisBlock; do { thisBlock = new DataBlock(inputStream); applicationData.Add(thisBlock); } // A zero-length block indicates the end of the data blocks while (thisBlock.DeclaredBlockSize != 0 && !thisBlock.TestState(ErrorState.EndOfInputStream) ); } SaveData(identificationBlock, applicationData); }
/// <summary> /// Constructor. /// </summary> /// <param name="inputStream"> /// The stream from which the image data is to be read, starting with /// the LZW minimum code size, and ending with a block terminator. /// </param> /// <param name="pixelCount"> /// Number of pixels in the image. /// </param> /// <remarks> /// The input stream is read, first into the LZW minimum code size, then /// into data blocks. Bytes are extracted from the data blocks into a /// datum until the datum contains enough bits to form a code; this code /// is then extracted from the datum and decoded into a pixel index. /// Once all data has been read, or a block terminator, /// end-of-information code or error condition is encountered, any /// remaining pixel indices not already populated default to zero. /// </remarks> public TableBasedImageData(Stream inputStream, int pixelCount) { #region guard against silly image sizes if (pixelCount < 1) { string message = "The pixel count must be greater than zero. " + "Supplied value was " + pixelCount; throw new ArgumentOutOfRangeException(nameof(pixelCount), message); } #endregion #region declare / initialise local variables _pixelIndexes = new byte[pixelCount]; int nextAvailableCode; // the next code to be added to the dictionary int currentCodeSize; int in_code; int previousCode; int code; int datum = 0; // temporary storage for codes read from the input stream int meaningfulBitsInDatum = 0; // number of bits of useful information held in the datum variable int firstCode = 0; // first code read from the stream since last clear code int indexInDataBlock = 0; int pixelIndex; // number of bytes still to be extracted from the current data block int bytesToExtract = 0; short[] prefix = new short[MaxStackSize]; byte[] suffix = new byte[MaxStackSize]; Stack <byte> pixelStack = new Stack <byte>(); #endregion // Initialize GIF data stream decoder. _lzwMinimumCodeSize = Read(inputStream); // number of bits initially used for LZW codes in image data int clearCode = ClearCode; int endOfInformation = EndOfInformation; nextAvailableCode = clearCode + 2; previousCode = NullCode; currentCodeSize = InitialCodeSize; #region guard against LZW code size being too large if (clearCode >= MaxStackSize) { string message = "LZW minimum code size: " + _lzwMinimumCodeSize + ". Clear code: " + clearCode + ". Max stack size: " + MaxStackSize; SetStatus(ErrorState.LzwMinimumCodeSizeTooLarge, message); return; } #endregion // TODO: what are prefix and suffix and why are we initialising them like this? for (code = 0; code < clearCode; code++) { prefix[code] = 0; suffix[code] = (byte)code; } #region decode LZW image data // Initialise block to an empty data block. This will be overwritten // first time through the loop with a data block read from the input // stream. var block = new DataBlock(0, new byte[0]); for (pixelIndex = 0; pixelIndex < pixelCount;) { if (pixelStack.Count == 0) { // There are no pixels in the stack at the moment, so... #region get some pixels and put them on the stack if (meaningfulBitsInDatum < currentCodeSize) { // Then we don't have enough bits in the datum to make // a code; we need to get some more from the current // data block, or we may need to read another data // block from the input stream #region get another byte from the current data block if (bytesToExtract == 0) { // Then we've extracted all the bytes from the // current data block, so... #region read the next data block from the stream block = ReadDataBlock(inputStream); bytesToExtract = block.ActualBlockSize; // Point to the first byte in the new data block indexInDataBlock = 0; if (block.TestState(ErrorState.DataBlockTooShort)) { // then we've reached the end of the stream // prematurely break; } if (bytesToExtract == 0) { // then it's a block terminator, end of the // image data (this is a data block other than // the first one) break; } #endregion } // Append the contents of the current byte in the data // block to the beginning of the datum int newDatum = block[indexInDataBlock] << meaningfulBitsInDatum; datum += newDatum; // so we've now got 8 more bits of information in the // datum. meaningfulBitsInDatum += 8; // Point to the next byte in the data block indexInDataBlock++; // We've one less byte still to read from the data block // now. bytesToExtract--; // and carry on reading through the data block continue; #endregion } #region get the next code from the datum // Get the least significant bits from the read datum, up // to the maximum allowed by the current code size. code = datum & GetMaximumPossibleCode(currentCodeSize); // Drop the bits we've just extracted from the datum. datum >>= currentCodeSize; // Reduce the count of meaningful bits held in the datum meaningfulBitsInDatum -= currentCodeSize; #endregion #region interpret the code #region end of information? if (code == endOfInformation) { // We've reached an explicit marker for the end of the // image data. break; } #endregion #region code not in dictionary? if (code > nextAvailableCode) { // We expect the code to be either one which is already // in the dictionary, or the next available one to be // added. If it's neither of these then abandon // processing of the image data. string message = "Next available code: " + nextAvailableCode + ". Last code read from input stream: " + code; SetStatus(ErrorState.CodeNotInDictionary, message); break; } #endregion #region clear code? if (code == clearCode) { // We can get a clear code at any point in the image // data, this is an instruction to reset the decoder // and empty the dictionary of codes. currentCodeSize = InitialCodeSize; nextAvailableCode = ClearCode + 2; previousCode = NullCode; // Carry on reading from the input stream. continue; } #endregion #region first code since last clear code? if (previousCode == NullCode) { // This is the first code read since the start of the // image data or the most recent clear code. // There's no previously read code in memory yet, so // get the pixel index for the current code and add it // to the stack. pixelStack.Push(suffix[code]); previousCode = code; firstCode = code; // and carry on to the next pixel continue; } #endregion in_code = code; if (code == nextAvailableCode) { pixelStack.Push((byte)firstCode); code = previousCode; } while (code > clearCode) { pixelStack.Push(suffix[code]); code = prefix[code]; } #endregion firstCode = (suffix[code]) & 0xff; pixelStack.Push((byte)firstCode); #region add a new string to the string table // This fix is based off of ImageSharp's LzwDecoder.cs: // https://github.com/SixLabors/ImageSharp/blob/8899f23c1ddf8044d4dea7d5055386f684120761/src/ImageSharp/Formats/Gif/LzwDecoder.cs // Fix for Gifs that have "deferred clear code" as per here : // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 if (nextAvailableCode < MaxStackSize) { // TESTME: constructor - next available code >- _maxStackSize prefix[nextAvailableCode] = (short)previousCode; suffix[nextAvailableCode] = (byte)firstCode; nextAvailableCode++; #endregion #region do we need to increase the code size? if ((nextAvailableCode & GetMaximumPossibleCode(currentCodeSize)) == 0) { // We've reached the largest code possible for this size if (nextAvailableCode < MaxStackSize) { // so increase the code size by 1 currentCodeSize++; } } #endregion } previousCode = in_code; #endregion } // Pop all the pixels currently on the stack off, and add them // to the return value. _pixelIndexes[pixelIndex] = pixelStack.Pop(); pixelIndex++; } #endregion #region check input stream contains enough data to fill the image if (pixelIndex < pixelCount) { string message = "Expected pixel count: " + pixelCount + ". Actual pixel count: " + pixelIndex; SetStatus(ErrorState.TooFewPixelsInImageData, message); } #endregion }