// /// <summary> // /// Tests the encoder with 1,000,000 random pixel values. // /// </summary> // [Test] // public void Test1000000Pixels() // { // ReportStart(); // RandomFill( 1000000 ); // ReportEnd(); // } #endregion #region private RandomFill method /// <summary> /// Fills _ip with random values /// </summary> /// <param name="count"> /// Number of values to add /// </param> private void RandomFill(int count) { Random r = new Random(); for (int blockiness = 1; blockiness < 20; blockiness++) { _ip = new IndexedPixels(); byte[] bytes = new byte[count]; r.NextBytes(bytes); byte lastByte = 0; foreach (byte b in bytes) { // Add a new value to the collection only if we throw a 1 int diceThrow = r.Next(1, blockiness); if (diceThrow == 1) { _ip.Add(b); lastByte = b; } else { // otherwise add the previous value again // (this more accurately simulates colour distribution in // an actual image) _ip.Add(lastByte); } } TestIt(); } }
private void AnalyseSingleImage() { if (_distinctColours.Count > 256) { switch (_quantizerType) { case QuantizerType.NeuQuant: AnalyseSingleImageWithNeuQuant(); break; case QuantizerType.Octree: AnalyseSingleImageWithOctree(); break; } } else { // few enough colours to create a colour table directly without // quantization. CreateDirectColourTable(); } // Work out the indices in the colour table of each of the pixels // in the supplied image. _indexedPixels = GetIndexedPixels(_imageColours); }
private void AnalyseManyImages() { // Work out the colour table for the pixels in each of the // supplied images GetColours(_imagesToStudy); if (_distinctColours.Count > 256) { CreateColourTableUsingNeuQuant(_colourQuality); // TODO: work out how to create a global colour table using Octree quantizer } else { CreateDirectColourTable(); } string analysingFrameCounterText = "Analysing frame"; AddCounter(analysingFrameCounterText, _imagesToStudy.Count); // Work out the indices in the colour table of each of the pixels // in each of the supplied images. _indexedPixelsCollection = new Collection <IndexedPixels>(); for (int i = 0; i < _imagesToStudy.Count; i++) { MyProgressCounters[analysingFrameCounterText].Value = i + 1; IndexedPixels indexedPixels = GetIndexedPixels(_imagesColours[i]); _indexedPixelsCollection.Add(indexedPixels); } RemoveCounter(analysingFrameCounterText); }
public void DefaultConstructor() { ReportStart(); byte[] bytes = new byte[] { 24, 39, 2 }; // Test the constructor _ip = new IndexedPixels(); // Test the Add method foreach (byte b in bytes) { _ip.Add(b); } Assert.AreEqual(bytes.Length, _ip.Count); // Test the get accessor of the indexer for (int i = 0; i < bytes.Length; i++) { Assert.AreEqual(bytes[i], _ip[i]); } // Test the set accessor of the indexer _ip[1] = 246; Assert.AreEqual(246, _ip[1]); ReportEnd(); }
/// <summary> /// Encodes and writes pixel data to the supplied stream /// </summary> /// <param name="indexedPixels"> /// Collection of indices of the pixel colours within the active colour /// table. /// </param> /// <param name="outputStream"> /// The stream to write to. /// </param> private static void WritePixels(IndexedPixels indexedPixels, Stream outputStream) { LzwEncoder encoder = new LzwEncoder(indexedPixels); encoder.Encode(outputStream); }
public void BlockTerminatorTest() { ReportStart(); byte[] bytes = new byte[] { 0x08, // LZW minimum code size 0x05, // block size = 5 // 5 bytes of LZW encoded data follows 0x00, 0x51, 0xFC, 0x1B, 0x28, 0x06, // block size = 6 // 6 bytes of LZW encoded data follows 0x70, 0xA0, 0xC1, 0x83, 0x01, 0x01, 0x00, // block terminator - end of table based image data }; MemoryStream s = new MemoryStream(); s.Write(bytes, 0, bytes.Length); s.Seek(0, SeekOrigin.Begin); int pixelCount = WikipediaExample.FrameSize.Width * WikipediaExample.FrameSize.Height; _tbid = new TableBasedImageData(s, pixelCount); Assert.AreEqual(ErrorState.Ok, _tbid.ConsolidatedState); Assert.AreEqual(15, _tbid.Pixels.Count); Assert.AreEqual(ErrorState.Ok, _tbid.ConsolidatedState); Assert.AreEqual(8, _tbid.LzwMinimumCodeSize); Assert.AreEqual(9, _tbid.InitialCodeSize); Assert.AreEqual(Math.Pow(2, 8), _tbid.ClearCode); Assert.AreEqual(Math.Pow(2, 8) + 1, _tbid.EndOfInformation); IndexedPixels expectedIndices = new IndexedPixels(); expectedIndices.Add(40); // first pixel is black - index 0 in colour table expectedIndices.Add(255); // 2nd pixel is white - index 255 in colour table expectedIndices.Add(255); // 3rd pixel expectedIndices.Add(255); // 4th pixel expectedIndices.Add(40); // 5th pixel expectedIndices.Add(255); // 6th pixel expectedIndices.Add(255); // 7th pixel expectedIndices.Add(255); // 8th pixel expectedIndices.Add(255); // 9th pixel expectedIndices.Add(255); // 10th pixel expectedIndices.Add(255); // 11th pixel expectedIndices.Add(255); // 12th pixel expectedIndices.Add(255); // 13th pixel expectedIndices.Add(255); // 14th pixel expectedIndices.Add(255); // 15th pixel for (int i = 0; i < 15; i++) { Assert.AreEqual(expectedIndices[i], _tbid.Pixels[i], "pixel " + i); } ReportEnd(); }
private void DataBlockTooShort(bool xmlDebugging) { byte[] bytes = new byte[] { 0x08, // LZW minimum code size 0x0b, // block size = 11 // 11 bytes of LZW encoded data follows (actually there's only 10 for this test) 0x00, 0x51, 0xFC, 0x1B, 0x28, 0x70, 0xA0, 0xC1, 0x83, 0x01, //0x01, // 0x00 // block terminator }; MemoryStream s = new MemoryStream(); s.Write(bytes, 0, bytes.Length); s.Seek(0, SeekOrigin.Begin); int pixelCount = WikipediaExample.FrameSize.Width * WikipediaExample.FrameSize.Height; _tbid = new TableBasedImageData(s, pixelCount, xmlDebugging); Assert.AreEqual(15, _tbid.Pixels.Count); Assert.AreEqual(ErrorState.DataBlockTooShort | ErrorState.TooFewPixelsInImageData, _tbid.ConsolidatedState); Assert.AreEqual(8, _tbid.LzwMinimumCodeSize); Assert.AreEqual(9, _tbid.InitialCodeSize); Assert.AreEqual(Math.Pow(2, 8), _tbid.ClearCode); Assert.AreEqual(Math.Pow(2, 8) + 1, _tbid.EndOfInformation); IndexedPixels expectedIndices = new IndexedPixels(); expectedIndices.Add(0); // first pixel expectedIndices.Add(0); // 2nd pixel expectedIndices.Add(0); // 3rd pixel expectedIndices.Add(0); // 4th pixel expectedIndices.Add(0); // 5th pixel expectedIndices.Add(0); // 6th pixel expectedIndices.Add(0); // 7th pixel expectedIndices.Add(0); // 8th pixel expectedIndices.Add(0); // 9th pixel expectedIndices.Add(0); // 10th pixel expectedIndices.Add(0); // 11th pixel expectedIndices.Add(0); // 12th pixel expectedIndices.Add(0); // 13th pixel expectedIndices.Add(0); // 14th pixel expectedIndices.Add(0); // 15th pixel for (int i = 0; i < 15; i++) { Assert.AreEqual(expectedIndices[i], _tbid.Pixels[i], "pixel " + i); } if (xmlDebugging) { Assert.AreEqual(ExpectedDebugXml, _tbid.DebugXml); } }
/// <summary> /// Constructor. /// </summary> /// <param name="pixels"> /// Indices in the active colour table of the colours of the pixel /// making up the image. /// </param> /// <exception cref="ArgumentNullException"> /// The supplied pixel collection is null. /// </exception> public LzwEncoder(IndexedPixels pixels) { if (pixels == null) { throw new ArgumentNullException("pixels"); } _pixels = pixels; // _initCodeSize = Math.Max(2, colourDepth); _initCodeSize = 8; // only seems to work reliably when 8, even if this is sometimes larger than needed }
private void CodeNotInDictionary(bool xmlDebugging) { byte[] bytes = WikipediaExample.ImageDataBytes; bytes[4] = 0xFF; // put an unexpected code into the stream MemoryStream s = new MemoryStream(); s.Write(bytes, 0, bytes.Length); s.Seek(0, SeekOrigin.Begin); int pixelCount = WikipediaExample.FrameSize.Width * WikipediaExample.FrameSize.Height; _tbid = new TableBasedImageData(s, pixelCount, xmlDebugging); Assert.IsTrue(_tbid.TestState(ErrorState.CodeNotInDictionary)); Assert.AreEqual(15, _tbid.Pixels.Count); Assert.AreEqual(8, _tbid.LzwMinimumCodeSize); Assert.AreEqual(9, _tbid.InitialCodeSize); Assert.AreEqual(Math.Pow(2, 8), _tbid.ClearCode); Assert.AreEqual(Math.Pow(2, 8) + 1, _tbid.EndOfInformation); IndexedPixels expectedIndices = new IndexedPixels(); // Check all the pixels have been zero-filled expectedIndices.Add(0); // first pixel expectedIndices.Add(0); // 2nd pixel expectedIndices.Add(0); // 3rd pixel expectedIndices.Add(0); // 4th pixel expectedIndices.Add(0); // 5th pixel expectedIndices.Add(0); // 6th pixel expectedIndices.Add(0); // 7th pixel expectedIndices.Add(0); // 8th pixel expectedIndices.Add(0); // 9th pixel expectedIndices.Add(0); // 10th pixel expectedIndices.Add(0); // 11th pixel expectedIndices.Add(0); // 12th pixel expectedIndices.Add(0); // 13th pixel expectedIndices.Add(0); // 14th pixel expectedIndices.Add(0); // 15th pixel for (int i = 0; i < 15; i++) { Assert.AreEqual(expectedIndices[i], _tbid.Pixels[i], "pixel " + i); } if (xmlDebugging) { Assert.AreEqual(ExpectedDebugXml, _tbid.DebugXml); } }
/// <summary> /// Gets the indices of the colours of each of the supplied pixels /// within the colour table. /// </summary> /// <param name="pixelColours"> /// A collection of the colours for which to get the indices in the /// colour table. /// </param> /// <returns> /// A collection of the indices of the colours of each of the supplied /// pixels within the colour table. /// </returns> private IndexedPixels GetIndexedPixels(Color[] pixelColours) { IndexedPixels indexedPixels = new IndexedPixels(); // Take a copy of the distinct colours to make the IndexOf method // available string copyDistinctColoursCounterText = "Copying distinct colours"; AddCounter(copyDistinctColoursCounterText, _distinctColours.Count); Collection <Color> distinctColours = new Collection <Color>(); foreach (Color c in _distinctColours.Keys) { MyProgressCounters[copyDistinctColoursCounterText].Value++; distinctColours.Add(c); } RemoveCounter(copyDistinctColoursCounterText); int indexInColourTable; int red; int green; int blue; int numberOfPixels = pixelColours.Length; string indexingPixelsCounterText = "Mapping colours to indices in colour table"; AddCounter(indexingPixelsCounterText, numberOfPixels); for (int i = 0; i < numberOfPixels; i++) { MyProgressCounters[indexingPixelsCounterText].Value = i; red = pixelColours[i].R; green = pixelColours[i].G; blue = pixelColours[i].B; // Get the index in the colour table of the colour of this pixel if (_distinctColours.Count > 256) { indexInColourTable = _nq.Map(red, green, blue); } else { indexInColourTable = distinctColours.IndexOf(pixelColours[i]); } indexedPixels.Add((byte)indexInColourTable); } RemoveCounter(indexingPixelsCounterText); return(indexedPixels); }
public void IndexOutOfRangeSet() { ReportStart(); _ip = new IndexedPixels(); try { _ip[0] = 6; } catch (ArgumentOutOfRangeException ex) { string message = "Collection size: 0. Supplied index: 0."; StringAssert.Contains(message, ex.Message); ReportEnd(); throw; } }
public void CapacityConstructorAdd() { ReportStart(); _ip = new IndexedPixels(3); try { _ip.Add(1); } catch (NotSupportedException ex) { string message = "You cannot add pixels to this instance because it was " + "instantiated with a fixed size."; StringAssert.Contains(message, ex.Message); ReportEnd(); throw; } }
private void MissingPixels(bool xmlDebugging) { byte[] bytes = WikipediaExample.ImageDataBytes; MemoryStream s = new MemoryStream(); s.Write(bytes, 0, bytes.Length); s.Seek(0, SeekOrigin.Begin); // The stream contains 15 pixels, this image size implies 18 pixels _tbid = new TableBasedImageData(s, 18, xmlDebugging); Assert.AreEqual(18, _tbid.Pixels.Count); Assert.AreEqual(ErrorState.TooFewPixelsInImageData, _tbid.ConsolidatedState); Assert.AreEqual(8, _tbid.LzwMinimumCodeSize); Assert.AreEqual(9, _tbid.InitialCodeSize); Assert.AreEqual(Math.Pow(2, 8), _tbid.ClearCode); Assert.AreEqual(Math.Pow(2, 8) + 1, _tbid.EndOfInformation); IndexedPixels expectedIndices = new IndexedPixels(); expectedIndices.Add(0); // first pixel is black - index 0 in colour table expectedIndices.Add(1); // 2nd pixel is white - index 1 in colour table expectedIndices.Add(1); // 3rd pixel expectedIndices.Add(1); // 4th pixel expectedIndices.Add(0); // 5th pixel expectedIndices.Add(1); // 6th pixel expectedIndices.Add(1); // 7th pixel expectedIndices.Add(1); // 8th pixel expectedIndices.Add(1); // 9th pixel expectedIndices.Add(1); // 10th pixel expectedIndices.Add(1); // 11th pixel expectedIndices.Add(1); // 12th pixel expectedIndices.Add(1); // 13th pixel expectedIndices.Add(1); // 14th pixel expectedIndices.Add(1); // 15th pixel expectedIndices.Add(0); // 16th pixel expectedIndices.Add(0); // 17th pixel expectedIndices.Add(0); // 18th pixel for (int i = 0; i < 18; i++) { Assert.AreEqual(expectedIndices[i], _tbid.Pixels[i], "pixel " + i); } }
/// <summary> /// Converts the supplied image to a collection of pixel indices using /// the supplied colour table. /// Only used when the QuantizerType is set to UseSuppliedPalette /// </summary> /// <param name="act">The active colour table</param> /// <param name="image">The image</param> /// <returns></returns> private IndexedPixels MakeIndexedPixels(ColourTable act, Image image) { //AddCounter( counterText, pixelCount ); Bitmap bitmap = (Bitmap)image; IndexedPixels ip = new IndexedPixels(); for (int y = 0; y < image.Height; y++) { for (int x = 0; x < image.Width; x++) { Color c = bitmap.GetPixel(x, y); int index = FindClosest(c, act); ip.Add((byte)index); //MyProgressCounters[counterText].Value = ip.Count; } } //RemoveCounter( counterText ); return(ip); }
public void CapacityConstructor() { ReportStart(); byte[] bytes = new byte[] { 24, 39, 2 }; // Test the constructor _ip = new IndexedPixels(bytes.Length); // Test the set accessor of the indexer for (int i = 0; i < bytes.Length; i++) { _ip[i] = bytes[i]; } Assert.AreEqual(bytes.Length, _ip.Count); // Test the get accessor of the indexer for (int i = 0; i < bytes.Length; i++) { Assert.AreEqual(bytes[i], _ip[i]); } ReportEnd(); }
private void DataBlockTooShort( bool xmlDebugging ) { byte[] bytes = new byte[] { 0x08, // LZW minimum code size 0x0b, // block size = 11 // 11 bytes of LZW encoded data follows (actually there's only 10 for this test) 0x00, 0x51, 0xFC, 0x1B, 0x28, 0x70, 0xA0, 0xC1, 0x83, 0x01, //0x01, // 0x00 // block terminator }; MemoryStream s = new MemoryStream(); s.Write( bytes, 0, bytes.Length ); s.Seek( 0, SeekOrigin.Begin ); int pixelCount = WikipediaExample.FrameSize.Width * WikipediaExample.FrameSize.Height; _tbid = new TableBasedImageData( s, pixelCount, xmlDebugging ); Assert.AreEqual( 15, _tbid.Pixels.Count ); Assert.AreEqual( ErrorState.DataBlockTooShort | ErrorState.TooFewPixelsInImageData, _tbid.ConsolidatedState ); Assert.AreEqual( 8, _tbid.LzwMinimumCodeSize ); Assert.AreEqual( 9, _tbid.InitialCodeSize ); Assert.AreEqual( Math.Pow( 2, 8 ), _tbid.ClearCode ); Assert.AreEqual( Math.Pow( 2, 8 ) + 1, _tbid.EndOfInformation ); IndexedPixels expectedIndices = new IndexedPixels(); expectedIndices.Add( 0 ); // first pixel expectedIndices.Add( 0 ); // 2nd pixel expectedIndices.Add( 0 ); // 3rd pixel expectedIndices.Add( 0 ); // 4th pixel expectedIndices.Add( 0 ); // 5th pixel expectedIndices.Add( 0 ); // 6th pixel expectedIndices.Add( 0 ); // 7th pixel expectedIndices.Add( 0 ); // 8th pixel expectedIndices.Add( 0 ); // 9th pixel expectedIndices.Add( 0 ); // 10th pixel expectedIndices.Add( 0 ); // 11th pixel expectedIndices.Add( 0 ); // 12th pixel expectedIndices.Add( 0 ); // 13th pixel expectedIndices.Add( 0 ); // 14th pixel expectedIndices.Add( 0 ); // 15th pixel for( int i = 0; i < 15; i++ ) { Assert.AreEqual( expectedIndices[i], _tbid.Pixels[i], "pixel " + i ); } if( xmlDebugging ) { Assert.AreEqual( ExpectedDebugXml, _tbid.DebugXml ); } }
/// <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> /// <param name="xmlDebugging">Whether or not to create debug XML</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, bool xmlDebugging) : base(xmlDebugging) { #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("pixelCount", message); } #endregion #region declare / initialise local variables _pixels = new IndexedPixels(pixelCount); _dataBlocks = new Collection <DataBlock>(); 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 = 0; // 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 WriteDebugXmlElement("LzwMinimumCodeSize", _lzwMinimumCodeSize); 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); WriteDebugXmlFinish(); 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; } WriteDebugXmlStartElement("DataBlocks"); #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. DataBlock 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 = ((int)suffix[code]) & 0xff; #region add a new string to the string table if (nextAvailableCode >= _maxStackSize) { // TESTME: constructor - next available code >- _maxStackSize break; } pixelStack.Push((byte)firstCode); 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. _pixels[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 if (XmlDebugging) { WriteDebugXmlEndElement(); byte[] bytes = new byte[_pixels.Count]; _pixels.CopyTo(bytes, 0); WriteDebugXmlByteValues("IndexedPixels", bytes); WriteDebugXmlFinish(); } }
/// <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> /// <param name="xmlDebugging">Whether or not to create debug XML</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, bool xmlDebugging ) : base( xmlDebugging ) { #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( "pixelCount", message ); } #endregion #region declare / initialise local variables _pixels = new IndexedPixels( pixelCount ); _dataBlocks = new Collection<DataBlock>(); 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 = 0; // 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 WriteDebugXmlElement( "LzwMinimumCodeSize", _lzwMinimumCodeSize ); 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 ); WriteDebugXmlFinish(); 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; } WriteDebugXmlStartElement( "DataBlocks" ); #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. DataBlock 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 = ((int) suffix[code]) & 0xff; #region add a new string to the string table if( nextAvailableCode >= _maxStackSize ) { // TESTME: constructor - next available code >- _maxStackSize break; } pixelStack.Push( (byte) firstCode ); 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. _pixels[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 if( XmlDebugging ) { WriteDebugXmlEndElement(); byte[] bytes = new byte[_pixels.Count]; _pixels.CopyTo( bytes, 0 ); WriteDebugXmlByteValues( "IndexedPixels", bytes ); WriteDebugXmlFinish(); } }
public void Setup() { _ip = new IndexedPixels(); }
/// <summary> /// Constructor. /// </summary> /// <param name="pixels"> /// Indices in the active colour table of the colours of the pixel /// making up the image. /// </param> /// <exception cref="ArgumentNullException"> /// The supplied pixel collection is null. /// </exception> public LzwEncoder( IndexedPixels pixels ) { if( pixels == null ) { throw new ArgumentNullException( "pixels" ); } _pixels = pixels; // _initCodeSize = Math.Max(2, colourDepth); _initCodeSize = 8; // only seems to work reliably when 8, even if this is sometimes larger than needed }
public void BlockTerminatorTest() { ReportStart(); byte[] bytes = new byte[] { 0x08, // LZW minimum code size 0x05, // block size = 5 // 5 bytes of LZW encoded data follows 0x00, 0x51, 0xFC, 0x1B, 0x28, 0x06, // block size = 6 // 6 bytes of LZW encoded data follows 0x70, 0xA0, 0xC1, 0x83, 0x01, 0x01, 0x00, // block terminator - end of table based image data }; MemoryStream s = new MemoryStream(); s.Write( bytes, 0, bytes.Length ); s.Seek( 0, SeekOrigin.Begin ); int pixelCount = WikipediaExample.FrameSize.Width * WikipediaExample.FrameSize.Height; _tbid = new TableBasedImageData( s, pixelCount ); Assert.AreEqual( ErrorState.Ok, _tbid.ConsolidatedState ); Assert.AreEqual( 15, _tbid.Pixels.Count ); Assert.AreEqual( ErrorState.Ok, _tbid.ConsolidatedState ); Assert.AreEqual( 8, _tbid.LzwMinimumCodeSize ); Assert.AreEqual( 9, _tbid.InitialCodeSize ); Assert.AreEqual( Math.Pow( 2, 8 ), _tbid.ClearCode ); Assert.AreEqual( Math.Pow( 2, 8 ) + 1, _tbid.EndOfInformation ); IndexedPixels expectedIndices = new IndexedPixels(); expectedIndices.Add( 40 ); // first pixel is black - index 0 in colour table expectedIndices.Add( 255 ); // 2nd pixel is white - index 255 in colour table expectedIndices.Add( 255 ); // 3rd pixel expectedIndices.Add( 255 ); // 4th pixel expectedIndices.Add( 40 ); // 5th pixel expectedIndices.Add( 255 ); // 6th pixel expectedIndices.Add( 255 ); // 7th pixel expectedIndices.Add( 255 ); // 8th pixel expectedIndices.Add( 255 ); // 9th pixel expectedIndices.Add( 255 ); // 10th pixel expectedIndices.Add( 255 ); // 11th pixel expectedIndices.Add( 255 ); // 12th pixel expectedIndices.Add( 255 ); // 13th pixel expectedIndices.Add( 255 ); // 14th pixel expectedIndices.Add( 255 ); // 15th pixel for( int i = 0; i < 15; i++ ) { Assert.AreEqual( expectedIndices[i], _tbid.Pixels[i], "pixel " + i ); } ReportEnd(); }
private void MissingPixels( bool xmlDebugging ) { byte[] bytes = WikipediaExample.ImageDataBytes; MemoryStream s = new MemoryStream(); s.Write( bytes, 0, bytes.Length ); s.Seek( 0, SeekOrigin.Begin ); // The stream contains 15 pixels, this image size implies 18 pixels _tbid = new TableBasedImageData( s, 18, xmlDebugging ); Assert.AreEqual( 18, _tbid.Pixels.Count ); Assert.AreEqual( ErrorState.TooFewPixelsInImageData, _tbid.ConsolidatedState ); Assert.AreEqual( 8, _tbid.LzwMinimumCodeSize ); Assert.AreEqual( 9, _tbid.InitialCodeSize ); Assert.AreEqual( Math.Pow( 2, 8 ), _tbid.ClearCode ); Assert.AreEqual( Math.Pow( 2, 8 ) + 1, _tbid.EndOfInformation ); IndexedPixels expectedIndices = new IndexedPixels(); expectedIndices.Add( 0 ); // first pixel is black - index 0 in colour table expectedIndices.Add( 1 ); // 2nd pixel is white - index 1 in colour table expectedIndices.Add( 1 ); // 3rd pixel expectedIndices.Add( 1 ); // 4th pixel expectedIndices.Add( 0 ); // 5th pixel expectedIndices.Add( 1 ); // 6th pixel expectedIndices.Add( 1 ); // 7th pixel expectedIndices.Add( 1 ); // 8th pixel expectedIndices.Add( 1 ); // 9th pixel expectedIndices.Add( 1 ); // 10th pixel expectedIndices.Add( 1 ); // 11th pixel expectedIndices.Add( 1 ); // 12th pixel expectedIndices.Add( 1 ); // 13th pixel expectedIndices.Add( 1 ); // 14th pixel expectedIndices.Add( 1 ); // 15th pixel expectedIndices.Add( 0 ); // 16th pixel expectedIndices.Add( 0 ); // 17th pixel expectedIndices.Add( 0 ); // 18th pixel for( int i = 0; i < 18; i++ ) { Assert.AreEqual( expectedIndices[i], _tbid.Pixels[i], "pixel " + i ); } }
private void CodeNotInDictionary( bool xmlDebugging ) { byte[] bytes = WikipediaExample.ImageDataBytes; bytes[4] = 0xFF; // put an unexpected code into the stream MemoryStream s = new MemoryStream(); s.Write( bytes, 0, bytes.Length ); s.Seek( 0, SeekOrigin.Begin ); int pixelCount = WikipediaExample.FrameSize.Width * WikipediaExample.FrameSize.Height; _tbid = new TableBasedImageData( s, pixelCount, xmlDebugging ); Assert.IsTrue( _tbid.TestState( ErrorState.CodeNotInDictionary ) ); Assert.AreEqual( 15, _tbid.Pixels.Count ); Assert.AreEqual( 8, _tbid.LzwMinimumCodeSize ); Assert.AreEqual( 9, _tbid.InitialCodeSize ); Assert.AreEqual( Math.Pow( 2, 8 ), _tbid.ClearCode ); Assert.AreEqual( Math.Pow( 2, 8 ) + 1, _tbid.EndOfInformation ); IndexedPixels expectedIndices = new IndexedPixels(); // Check all the pixels have been zero-filled expectedIndices.Add( 0 ); // first pixel expectedIndices.Add( 0 ); // 2nd pixel expectedIndices.Add( 0 ); // 3rd pixel expectedIndices.Add( 0 ); // 4th pixel expectedIndices.Add( 0 ); // 5th pixel expectedIndices.Add( 0 ); // 6th pixel expectedIndices.Add( 0 ); // 7th pixel expectedIndices.Add( 0 ); // 8th pixel expectedIndices.Add( 0 ); // 9th pixel expectedIndices.Add( 0 ); // 10th pixel expectedIndices.Add( 0 ); // 11th pixel expectedIndices.Add( 0 ); // 12th pixel expectedIndices.Add( 0 ); // 13th pixel expectedIndices.Add( 0 ); // 14th pixel expectedIndices.Add( 0 ); // 15th pixel for( int i = 0; i < 15; i++ ) { Assert.AreEqual( expectedIndices[i], _tbid.Pixels[i], "pixel " + i ); } if( xmlDebugging ) { Assert.AreEqual( ExpectedDebugXml, _tbid.DebugXml ); } }
/// <summary> /// Encodes and writes pixel data to the supplied stream /// </summary> /// <param name="indexedPixels"> /// Collection of indices of the pixel colours within the active colour /// table. /// </param> /// <param name="outputStream"> /// The stream to write to. /// </param> private static void WritePixels( IndexedPixels indexedPixels, Stream outputStream ) { LzwEncoder encoder = new LzwEncoder( indexedPixels ); encoder.Encode( outputStream ); }
/// <summary> /// Fills _ip with random values /// </summary> /// <param name="count"> /// Number of values to add /// </param> private void RandomFill( int count ) { Random r = new Random(); for( int blockiness = 1; blockiness < 20; blockiness++ ) { _ip = new IndexedPixels(); byte[] bytes = new byte[count]; r.NextBytes( bytes ); byte lastByte = 0; foreach( byte b in bytes ) { // Add a new value to the collection only if we throw a 1 int diceThrow = r.Next( 1, blockiness ); if( diceThrow == 1 ) { _ip.Add( b ); lastByte = b; } else { // otherwise add the previous value again // (this more accurately simulates colour distribution in // an actual image) _ip.Add( lastByte ); } } TestIt(); } }
private void AnalyseSingleImage() { if( _distinctColours.Count > 256 ) { switch( _quantizerType ) { case QuantizerType.NeuQuant: AnalyseSingleImageWithNeuQuant(); break; case QuantizerType.Octree: AnalyseSingleImageWithOctree(); break; } } else { // few enough colours to create a colour table directly without // quantization. CreateDirectColourTable(); } // Work out the indices in the colour table of each of the pixels // in the supplied image. _indexedPixels = GetIndexedPixels( _imageColours ); }
/// <summary> /// Gets the indices of the colours of each of the supplied pixels /// within the colour table. /// </summary> /// <param name="pixelColours"> /// A collection of the colours for which to get the indices in the /// colour table. /// </param> /// <returns> /// A collection of the indices of the colours of each of the supplied /// pixels within the colour table. /// </returns> private IndexedPixels GetIndexedPixels( Color[] pixelColours ) { IndexedPixels indexedPixels = new IndexedPixels(); // Take a copy of the distinct colours to make the IndexOf method // available string copyDistinctColoursCounterText = "Copying distinct colours"; AddCounter( copyDistinctColoursCounterText, _distinctColours.Count ); Collection<Color> distinctColours = new Collection<Color>(); foreach( Color c in _distinctColours.Keys ) { MyProgressCounters[copyDistinctColoursCounterText].Value++; distinctColours.Add( c ); } RemoveCounter( copyDistinctColoursCounterText ); int indexInColourTable; int red; int green; int blue; int numberOfPixels = pixelColours.Length; string indexingPixelsCounterText = "Mapping colours to indices in colour table"; AddCounter( indexingPixelsCounterText, numberOfPixels ); for( int i = 0; i < numberOfPixels; i++ ) { MyProgressCounters[indexingPixelsCounterText].Value = i; red = pixelColours[i].R; green = pixelColours[i].G; blue = pixelColours[i].B; // Get the index in the colour table of the colour of this pixel if( _distinctColours.Count > 256 ) { indexInColourTable = _nq.Map( red, green, blue ); } else { indexInColourTable = distinctColours.IndexOf( pixelColours[i] ); } indexedPixels.Add( (byte) indexInColourTable ); } RemoveCounter( indexingPixelsCounterText ); return indexedPixels; }
/// <summary> /// Converts the supplied image to a collection of pixel indices using /// the supplied colour table. /// Only used when the QuantizerType is set to UseSuppliedPalette /// </summary> /// <param name="act">The active colour table</param> /// <param name="image">The image</param> /// <returns></returns> private IndexedPixels MakeIndexedPixels( ColourTable act, Image image ) { int pixelCount = image.Height * image.Width; string counterText = "Getting indices in colour table"; AddCounter( counterText, pixelCount ); Bitmap bitmap = (Bitmap) image; IndexedPixels ip = new IndexedPixels(); for( int y = 0; y < image.Height; y++ ) { for( int x = 0; x < image.Width; x++ ) { Color c = bitmap.GetPixel( x, y ); int index = FindClosest( c, act ); ip.Add( (byte) index ); MyProgressCounters[counterText].Value = ip.Count; } } RemoveCounter( counterText ); return ip; }