Ejemplo n.º 1
0
        public static Data2D <byte> ReadData2DBytes(this BinaryReader br)
        {
            Rectangle bounds = br.ReadRectangle();
            int       area   = bounds.Width * bounds.Height;

            Data2D <byte> result = new Data2D <byte>(area != 0 ? br.ReadBytes(area) : null, bounds);

            return(result);
        }
Ejemplo n.º 2
0
        public void Optimise()
        {
            Rectangle extents = heightmapData.FindTrimBounds(DefaultHeight);

            if (heightmapData.Bounds != extents)
            {
                heightmapData = heightmapData.CopyWithNewBounds(extents, DefaultHeight);
            }
        }
Ejemplo n.º 3
0
        public static void WriteData2DBytes(this BinaryWriter bw, Data2D <byte> data)
        {
            bw.Write(data.Bounds);
            int area = data.Bounds.Width * data.Bounds.Height;

            if (area != 0)
            {
                Debug.Assert(data.Data.Length == area);
                bw.Write(data.Data);
            }
        }
Ejemplo n.º 4
0
        /// <summary>Create a sprite from world-oriented color data</summary>
        public static Sprite MakeSpriteFromWorld(this Data2D <Color> data, GraphicsDevice device)
        {
            Debug.Assert(data.Width > 0 && data.Height > 0);

            var clientOrientedData = data.CopyFlipY();

            Texture2D texture = new Texture2D(device, clientOrientedData.Width, clientOrientedData.Height);

            texture.SetData(clientOrientedData.Data);
            return(new Sprite(texture, clientOrientedData.OriginInData));
        }
Ejemplo n.º 5
0
        public static Data2D <Color> CreateColorData(this MaskData mask, Color color)
        {
            var data = new Data2D <Color>(mask.Bounds);

            for (var y = data.StartY; y < data.EndY; y++)
            {
                for (var x = data.StartX; x < data.EndX; x++)
                {
                    data[x, y] = mask[x, y] ? color : Color.Transparent;
                }
            }

            return(data);
        }
Ejemplo n.º 6
0
        /// <summary>Set an area to a flat height given a mask of the base of the object</summary>
        public void SetFromFlatBaseMask(MaskData maskData, byte height)
        {
            // Ensure that there's enough room in the heightmap to contain mask...
            heightmapData = heightmapData.LazyCopyExpandToContain(maskData.Bounds, DefaultHeight);

            for (int y = maskData.StartY; y < maskData.EndY; y++)
            {
                for (int x = maskData.StartX; x < maskData.EndX; x++)
                {
                    if (maskData[x, y])
                    {
                        heightmapData[x, y] = height;
                    }
                }
            }
        }
Ejemplo n.º 7
0
        /// <summary>Set from a 1px deep alpha mask (such as for a railing)</summary>
        public void SetFromRailingMask(MaskData maskData)
        {
            heightmapData = heightmapData.LazyCopyExpandToContain(new Rectangle(maskData.OffsetX, 0, maskData.Width, 1), DefaultHeight);

            for (int x = maskData.StartX; x < maskData.EndX; x++)          // For each column in the image
            {
                for (int y = maskData.EndY - 1; y >= maskData.StartY; y--) // Search top-to-bottom
                {
                    if (maskData[x, y])
                    {
                        heightmapData[x, 0] = (byte)y;
                        goto nextColumn;
                    }
                }
nextColumn:
                ;
            }
        }
Ejemplo n.º 8
0
        /// <summary>Create a sprite from client-oriented color data</summary>
        public static Sprite MakeSprite(this Data2D <Color> data, GraphicsDevice device)
        {
            Debug.Assert(data.Width > 0 && data.Height > 0);

            Texture2D texture;

            if (device != null) // <- allows loading headless
            {
                texture = new Texture2D(device, data.Width, data.Height);
                texture.SetData(data.Data);
            }
            else
            {
                texture = null;
            }

            return(new Sprite(texture, data.OriginInData));
        }
Ejemplo n.º 9
0
        /// <summary>Convert color data to a trimmed 1-bit mask</summary>
        public static MaskData CreateMask(this Data2D <Color> data, Color color, bool inverse = false)
        {
            Rectangle trimBounds = data.FindTrimBounds(color, inverse);

            // TODO: Avoid this copy...
            Data2D <Color> trimData = data.CopyWithNewBounds(trimBounds);

            MaskData mask = new MaskData(trimData.Bounds);

            for (int y = trimData.StartY; y < trimData.EndY; y++)
            {
                for (int x = trimData.StartX; x < trimData.EndX; x++)
                {
                    mask[x, y] = ((trimData[x, y] == color) != inverse);
                }
            }

            return(mask);
        }
Ejemplo n.º 10
0
        public Heightmap(AnimationDeserializeContext context)
        {
            DefaultHeight = context.br.ReadByte();

            OneWay          = context.br.ReadBoolean();
            OneWayThickness = context.br.ReadByte();

            if (context.br.ReadBoolean())
            {
                Rectangle bounds = context.br.ReadRectangle();
                byte[]    data   = context.br.ReadBytes(bounds.Width * bounds.Height);
                heightmapData = new Data2D <byte>(data, bounds);
            }
            else
            {
                heightmapData = default(Data2D <byte>);
            }

            instructions = HeightmapInstructionExtensions.Deserialize(context);
        }
Ejemplo n.º 11
0
        /// <param name="slope">Number of pixels to travel backwards before traveling in the oblique direction</param>
        public void SetFromFrontEdge(MaskData maskData, int frontEdgeDepth, int depth, Oblique obliqueDirection, int slope, int offset)
        {
            Debug.Assert(depth > 0);
            Debug.Assert(slope > 0);

            // How far do we travel on the X axis as we go backwards?
            int pixelsTraveledSideways = ((depth + slope - 1) / slope - 1) * (int)obliqueDirection;
            int outputStartX           = Math.Min(maskData.StartX, maskData.StartX + pixelsTraveledSideways);
            int outputEndX             = Math.Max(maskData.EndX, maskData.EndX + pixelsTraveledSideways);

            // Ensure that there's enough room in the heightmap to contain the maximum extents of the processed mask...
            Rectangle outputPotentialBounds = new Rectangle(outputStartX, frontEdgeDepth, outputEndX - outputStartX, depth);

            heightmapData = heightmapData.LazyCopyExpandToContain(outputPotentialBounds, DefaultHeight);


            // Read the mask upwards to find the "lip" of the mask surface
            for (int x = maskData.StartX; x < maskData.EndX; x++)     // For each column in the image
            {
                for (int y = maskData.StartY; y < maskData.EndY; y++) // Search from bottom-to-top
                {
                    if (maskData[x, y])
                    {
                        // Found the lip at a given Y height, copy it backwards at the given pitch
                        for (int d = 0; d < depth; d++)
                        {
                            int zz = frontEdgeDepth + d;
                            int xx = x + (d / slope) * (int)obliqueDirection;

                            heightmapData[xx, zz] = (byte)(y - frontEdgeDepth + offset);
                        }

                        goto nextColumn;
                    }
                }

nextColumn:
                ;
            }
        }
Ejemplo n.º 12
0
        /// <summary>Perform a transform of the data around its origin, using the given transform matrix</summary>
        public Data2D <T> Transform(IntMatrix2 matrix)
        {
            // Local corners (inclusive)
            Point TL = new Point(OffsetX, OffsetY);
            Point TR = new Point(OffsetX + Width - 1, OffsetY);
            Point BL = new Point(OffsetX, OffsetY + Height - 1);
            Point BR = new Point(OffsetX + Width - 1, OffsetY + Height - 1);

            // Transformed corners:
            Point cornerA = matrix.Transform(TL);
            Point cornerB = matrix.Transform(TR);
            Point cornerC = matrix.Transform(BL);
            Point cornerD = matrix.Transform(BR);

            // Convert to rectangle:
            int       minX         = Math.Min(Math.Min(cornerA.X, cornerB.X), Math.Min(cornerC.X, cornerD.X));
            int       minY         = Math.Min(Math.Min(cornerA.Y, cornerB.Y), Math.Min(cornerC.Y, cornerD.Y));
            int       maxX         = Math.Max(Math.Max(cornerA.X, cornerB.X), Math.Max(cornerC.X, cornerD.X));
            int       maxY         = Math.Max(Math.Max(cornerA.Y, cornerB.Y), Math.Max(cornerC.Y, cornerD.Y));
            Rectangle outputBounds = new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1);

            // Now transform the data:
            Data2D <T> transformed = new Data2D <T>(outputBounds);

            for (int y = StartY; y < EndY; y++)
            {
                for (int x = StartX; x < EndX; x++)
                {
                    // INLINED: IntMatrix2.Transform
                    int outX = x * matrix.xToX + y * matrix.yToX;
                    int outY = x * matrix.xToY + y * matrix.yToY;

                    // INLINED: indexer operator (x2)
                    transformed.Data[(outX - transformed.OffsetX) + (outY - transformed.OffsetY) * transformed.Width]
                        = Data[(x - OffsetX) + (y - OffsetY) * Width];
                }
            }

            return(transformed);
        }
Ejemplo n.º 13
0
        // TODO: This is disabled at the moment, because it will probably make authoring difficult...
        //       (Also it's less of a big deal than physics heightmaps)
        public void OptimiseForShadowReceiver()
        {
            if (heightmapData.Data == null)
            {
                return;                                  // <- nothing to optimise
            }
            Debug.Assert(heightmapData.Data.Length > 0); // <- Data2D should be enforcing this condition

            // See if we have a heightmap filled with just a single value:
            byte startValue = heightmapData.Data[0];

            for (int i = 0; i < heightmapData.Data.Length; i++)
            {
                if (heightmapData.Data[i] != startValue)
                {
                    goto hasMoreThanOneValue;
                }
            }
            // Convert to fixed-height:
            DefaultHeight = startValue;
            heightmapData = new Data2D <byte>();
            return;

hasMoreThanOneValue:

            Rectangle extents = heightmapData.FindTrimBoundsForShadowReceiver();

            // Assert that we're shrinking (because if we expand, then we're adding in wrong data, due to DefaultHeight)
            Debug.Assert(extents.Left >= heightmapData.Bounds.Left);
            Debug.Assert(extents.Right <= heightmapData.Bounds.Right);
            Debug.Assert(extents.Top >= heightmapData.Bounds.Top);
            Debug.Assert(extents.Bottom <= heightmapData.Bounds.Bottom);

            if (heightmapData.Bounds != extents)
            {
                heightmapData = heightmapData.CopyWithNewBounds(extents, DefaultHeight);
            }
        }
Ejemplo n.º 14
0
        /// <summary>Set heights from a top-surface mask where the front edge is at a particular depth</summary>
        /// <param name="maskData">The 1-bit mask representing the top surface</param>
        /// <param name="frontEdgeDepth">The depth of the front edge of pixels in the mask</param>
        /// <param name="perspective">Oblique direction that the mask projects backwards towards</param>
        public void SetFromObliqueTopMask(MaskData maskData, int frontEdgeDepth, Oblique oblique)
        {
            // Ensure that there's enough room in the heightmap to contain the maximum extents of the mask
            Rectangle outputPotentialBounds = maskData.Bounds;

            outputPotentialBounds.Y = frontEdgeDepth; // The usable Z range = [frontEdgeDepth, frontEdgeDepth + Height)
            heightmapData           = heightmapData.LazyCopyExpandToContain(outputPotentialBounds, DefaultHeight);

            // Note: Y axis seeks from the top downwards (from back to front)
            for (int y = maskData.EndY - 1; y >= maskData.StartY; y--)
            {
                for (int x = maskData.StartX; x < maskData.EndX; x++)
                {
                    if (maskData[x, y])
                    {
                        int height = GetHeightByWalkingObliqueForward(maskData, frontEdgeDepth, oblique, x, y);
                        int z      = y - height;

                        heightmapData[x, z] = (byte)height; // (If height overflows... at least it will be obvious)
                    }
                }
            }
        }
Ejemplo n.º 15
0
 public void ClearToHeight(byte height)
 {
     DefaultHeight = height;
     heightmapData = default(Data2D <byte>);
 }
Ejemplo n.º 16
0
        public static Rectangle FindTrimBounds(this Data2D <byte> data, byte defaultValue)
        {
            if (data.Data == null)
            {
                return(Rectangle.Empty);
            }

            // NOTE: Bounds are found on non-offset data, and offset is applied at the end

            int top = 0;

            // Search downwards to find top row with a matching pixel...
            for (; top < data.Height; top++)
            {
                for (int x = 0; x < data.Width; x++)
                {
                    if (data.Data[x + top * data.Width] != defaultValue)
                    {
                        goto foundTop;
                    }
                }
            }
            return(Rectangle.Empty); // No matching data found

foundTop:

            int bottom = data.Height - 1, left = 0, right = data.Width - 1;

            // Search upwards to find bottom row with a matching pixel...
            for (; bottom > top; bottom--) // <- (exit if we reach the 'top' row: 1px tall)
            {
                for (int x = 0; x < data.Width; x++)
                {
                    if (data.Data[x + bottom * data.Width] != defaultValue)
                    {
                        goto foundBottom;
                    }
                }
            }

foundBottom:

            // Search left to find left column with a matching pixel...
            for (; left < data.Width; left++)
            {
                for (int y = top; y <= bottom; y++)
                {
                    if (data.Data[left + y * data.Width] != defaultValue)
                    {
                        goto foundLeft;
                    }
                }
            }

foundLeft:

            // Search right to find right column with a matching pixel...
            for (; right > left; right--) // <- (exit if we reach the 'left' column: 1px wide)
            {
                for (int y = top; y <= bottom; y++)
                {
                    if (data.Data[right + y * data.Width] != defaultValue)
                    {
                        goto foundRight;
                    }
                }
            }

foundRight:

            return(new Rectangle(left + data.OffsetX, top + data.OffsetY, right - left + 1, bottom - top + 1));
        }
Ejemplo n.º 17
0
        public void SetFromObliqueSide(MaskData maskData, Oblique obliqueDirection, int offset)
        {
            // Straight and Right use the same input direction (because Straight input does not make sense, but Straight output is ok)
            int inputReadDirection = 1;
            int x = maskData.StartX;

            if (obliqueDirection == Oblique.Left)
            {
                inputReadDirection = -1;
                x = maskData.EndX - 1;
            }

            int y;

            while (x >= maskData.StartX && x < maskData.EndX)
            {
                for (y = maskData.StartY; y < maskData.EndY; y++) // read bottom-to-top
                {
                    if (maskData[x, y])
                    {
                        goto foundStartPosition;
                    }
                }
                x += inputReadDirection;
            }

            // No data found!
            return;

foundStartPosition:

            // Ensure that there's enough room in the heightmap to contain the maximum extents of the processed mask...
            {
                int left, right;
                if (inputReadDirection == 1)
                {
                    left  = x;
                    right = maskData.EndX - 1;
                }
                else // reading right-to-left
                {
                    left  = maskData.StartX;
                    right = x;
                }
                // Account for offset:
                left  += Math.Min(offset, 0);
                right += Math.Max(offset, 0);
                int front = y;
                int back  = front + (right - left); // can move back one pixel for each column of input

                Rectangle outputPotentialBounds = new Rectangle(left, front, (right - left) + 1, (back - front) + 1);
                heightmapData = heightmapData.LazyCopyExpandToContain(outputPotentialBounds, DefaultHeight);
            }


            // Convert mask to heightmap:
            int writeX = x;
            int writeZ = y;
            int baseY  = y;

            while (x >= maskData.StartX && x < maskData.EndX) // For each column to end of image
            {
                y = baseY;                                    // Count pixels from base upwards
                while (y < maskData.EndY && maskData[x, y])
                {
                    y++;
                }

                int height = y - baseY;
                if (height > 0)
                {
                    int i = 0;
                    do
                    {
                        heightmapData[writeX + i * Math.Sign(offset), writeZ] = (byte)Math.Min(byte.MaxValue, height);
                        i++;
                    } while(i < Math.Abs(offset));
                }

                // Move input:
                x += inputReadDirection;
                baseY++;

                // Move output:
                writeX += (int)obliqueDirection;
                writeZ++;
            }
        }
Ejemplo n.º 18
0
        // TODO: If we can pass in a query direction, we can super-optimise at the top and bottom sides
        public static Rectangle FindTrimBoundsForShadowReceiver(this Data2D <byte> data)
        {
            if (data.Data == null)
            {
                return(Rectangle.Empty);
            }

            // NOTE: Bounds are found on non-offset data, and offset is applied at the end

            // Top must all be the same value (due to angular queries)
            byte topValue = data.Data[0];
            int  top      = 0;

            for (; top < data.Height; top++)
            {
                for (int x = 0; x < data.Width; x++)
                {
                    if (data.Data[x + top * data.Width] != topValue)
                    {
                        goto foundTop;
                    }
                }
            }
foundTop:

            int bottom = data.Height - 1, left = 0, right = data.Width - 1;

            // Bottom must all be the same value  (due to angular queries)
            byte bottomValue = data.Data[bottom * data.Width];

            for (; bottom > top; bottom--) // <- (exit if we reach the 'top' row: 1px tall)
            {
                for (int x = 0; x < data.Width; x++)
                {
                    if (data.Data[x + bottom * data.Width] != bottomValue)
                    {
                        goto foundBottom;
                    }
                }
            }
foundBottom:

            // Left queries inwards, so columns must match
            for (; left + 1 < data.Width; left++)
            {
                for (int y = top; y <= bottom; y++)
                {
                    if (data.Data[left + 1 + y * data.Width] != data.Data[left + y * data.Width])
                    {
                        goto foundLeft;
                    }
                }
            }
foundLeft:

            // Right queries inwards, so columns must match
            for (; right - 1 > left; right--) // <- (exit if we reach the 'left' column: 1px wide)
            {
                for (int y = top; y <= bottom; y++)
                {
                    if (data.Data[right - 1 + y * data.Width] != data.Data[right + y * data.Width])
                    {
                        goto foundRight;
                    }
                }
            }
foundRight:

            return(new Rectangle(left + data.OffsetX, top + data.OffsetY, right - left + 1, bottom - top + 1));
        }
Ejemplo n.º 19
0
        // TODO: Factor this out such that frames can be appended on-the-fly (for capturing "animated screenshots").
        //
        //       May want to do threading  (outside this class). Could thread the following:
        //       - Pulling GPU surfaces (want to return surfaces quikcly)
        //       - GIF encoding (maybe fast enough to combine, rather than take buffer re-cache hit and thread costs)
        //       - File I/O (want to avoid blocking useful work while writing to file, and built-in async allocates, uses a thread pool anyway??)
        //       Debatable about what, if anything, to combine.


        public void WriteAnimation(Animation animation)
        {
            if (animation == null)
            {
                throw new ArgumentNullException("animation");
            }
            if (animation.FrameCount == 0)
            {
                return;
            }

            if (stream == null)
            {
                throw new InvalidOperationException("No stream set");
            }

            Rectangle animationBounds           = animation.GetSoftRenderBounds(true);
            Position  accumulatedGameplayMotion = Position.Zero;

            bool animated = animation.FrameCount > 1;

            // Preconditions:
            if (animationBounds.Width > ushort.MaxValue || animationBounds.Height > ushort.MaxValue)
            {
                return;
            }

            bool transparency = true;

            // GIF: Header
            {
                WriteBuffer(gifHeader);
            }

            // GIF: Logical Screen Descriptor
            {
                WriteUShort((ushort)animationBounds.Width);
                WriteUShort((ushort)animationBounds.Height);

                byte packed = 0;
                packed |= 1 << 7; // Global colour table present
                packed |= 7 << 4; // Bits-per-channel minus 1, in source data
                packed |= 0 << 3; // Is GCT priority-sorted?
                packed |= 7 << 0; // Global colour table size = 2^(N+1)
                WriteByte(packed);

                WriteByte(0); // Background colour index into GCT (or 0 if not present)
                WriteByte(0); // Pixel aspect ratio, or 0 for "not-specified"
            }

            // GIF: Global Color Table
            // TODO: Consider using pre-built NES palette? (Especially for game export)
            // TODO: Add support for local color tables
            // Reserve and clear space in output buffer:
            const int colorTableBytes = 256 * 3; // 256 RGB Colors
            int       gctStart        = bufferPosition;

            EnsureBufferSpace(colorTableBytes);
            bufferPosition += colorTableBytes;
            Array.Clear(buffer, gctStart, colorTableBytes);
            int gctCount = 1; // <- first value is always pure black, and transparent if transparency is enabled

            // GIF: NETSCAPE2.0 Application Extension Block
            if (animated)
            {
                WriteBuffer(netscapeBlock);
            }

            foreach (var frame in animation.Frames)
            {
                var data = frame.SoftRender();

                // HACK: If the image has zero size (blank, cropped to nothing), replace it with 1px of transparency
                //       (Because I'm not fiddling around trying to figure out how to encode a zero-sized frame, or how compatible that is, right now -AR)
                if (data.Width == 0 || data.Height == 0)
                {
                    data = new Data2D <Color>(animationBounds.X, animationBounds.Y, 1, 1);
                }

                accumulatedGameplayMotion += frame.positionDelta;
                Point motion = accumulatedGameplayMotion.ToWorldZero.FlipY(); // Convert to texture space
                data.OffsetX += motion.X;
                data.OffsetY += motion.Y;

                int delayTime = (frame.delay * 100) / 60; // Convert ticks at 60FPS to ticks at 100FPS (the GIF standard)
                // NOTE: Browsers do retarded things with timings of 0 or 1. Some even do stupid things with 2-5.
                //       See http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser for details
                delayTime = System.Math.Max(2, System.Math.Min(delayTime, ushort.MaxValue));

                // GIF: Graphic Control Extension
                {
                    WriteByte(0x21); // extension block
                    WriteByte(0xF9); // graphics control label
                    WriteByte(0x04); // block size in bytes (fixed)

                    byte packed = 0;
                    packed |= (byte)((animated ? 2u : 0u) << 2);    // Disposal method (0 = none specified, 1 = do not clear, 2 = clear to BG)
                    packed |= 0 << 1;                               // User input flag
                    packed |= (byte)((transparency ? 1u : 0) << 0); // Transparent colour flag
                    WriteByte(packed);

                    WriteUShort((ushort)(animated ? delayTime : 0)); // Delay time
                    WriteByte(0);                                    // Transparent colour index // TODO: Put this outside the table, so that non-transparent images don't have fixed black entry in palette.
                    WriteByte(0);                                    // Block terminator
                }

                // GIF: Image descriptor
                {
                    WriteByte(0x2C);                                         // Image separator
                    WriteUShort((ushort)(data.OffsetX - animationBounds.X)); // Image left
                    WriteUShort((ushort)(data.OffsetY - animationBounds.Y)); // Image top
                    WriteUShort((ushort)data.Width);                         // Image width
                    WriteUShort((ushort)data.Height);                        // Image height

                    byte packed = 0;
                    packed |= 0 << 7; // Local colour table
                    packed |= 0 << 6; // Interlace
                    packed |= 0 << 5; // Is LCT priority sorted?
                    packed |= 0 << 0; // Local colour table size = 2^(N+1)
                    WriteByte(packed);

                    // NOTE: Local colour table would folow if specified
                }

                // GIF: Image Data (with LZW compression)
                {
                    WriteByte(8); // LZW minimum code size (hard-coding for 256-colour images for now)
                    WriteByte(0); // Placeholder for chunk size
                    PendingBitsAndAndChunks pending = new PendingBitsAndAndChunks();

                    // Code table is a series of unbalanced binary trees of available suffixes, one for each prefix
                    LZWTableEntry[] codeTable;
                    ushort[]        treeRoots;
                    if (codeTableStorage == null)
                    {
                        codeTable = codeTableStorage = new LZWTableEntry[4096 - codeStart]; // maximum 12-bit codes
                        treeRoots = treeRootsStorage = new ushort[4096];
                    }
                    else
                    {
                        codeTable = codeTableStorage;
                        treeRoots = treeRootsStorage;
                        Array.Clear(treeRoots, 0, treeRoots.Length);
                    }

                    int codeCount    = 0; // <- number of used entries in the (stored) code table
                    int codeBitsUsed = 9;

                    // Always start with clear code:
                    WriteVariableBitsChunked(clearCode, codeBitsUsed, ref pending);

                    // Setup active prefix:
                    uint activePrefix = (uint)GetColorTableIndex(buffer, gctStart, ref gctCount, data.Data[0], transparency);
                    Debug.Assert(activePrefix < 256);

                    // Loop through remaining indicies:
                    int imageSize = data.Width * data.Height;
                    for (int i = 1; i < imageSize; i++)
                    {
                        uint activeSuffix = (uint)GetColorTableIndex(buffer, gctStart, ref gctCount, data.Data[i], transparency);
                        Debug.Assert(activeSuffix < 256);

                        uint lastTreePosition = 0; // <- sentinal value for the root
                        uint foundSuffix      = 0;
                        // Search for matching entry:
                        {
                            uint treePosition = treeRoots[activePrefix];
                            while (treePosition != 0)
                            {
                                lastTreePosition = treePosition;
                                foundSuffix      = codeTable[treePosition - codeStart].SuffixValue;

                                if (activeSuffix == foundSuffix)
                                {
                                    // FOUND:
                                    activePrefix = treePosition;
                                    goto nextIndex;
                                }
                                else if (activeSuffix < foundSuffix)
                                {
                                    treePosition = codeTable[treePosition - codeStart].LowerIndex;
                                }
                                else // activeSuffix > foundSuffix
                                {
                                    treePosition = codeTable[treePosition - codeStart].HigherIndex;
                                }
                            }
                        }

                        // NOT FOUND:

                        // Write the code we do know about
                        WriteVariableBitsChunked(activePrefix, codeBitsUsed, ref pending);

                        // If we fill up the code table, reset it
                        if (codeCount == codeTable.Length)
                        {
                            WriteVariableBitsChunked(clearCode, codeBitsUsed, ref pending);
                            Array.Clear(treeRoots, 0, treeRoots.Length);
                            codeCount    = 0;
                            codeBitsUsed = 9;
                        }
                        else // Add to the code table
                        {
                            // Insert into unbalanced binary tree (prefix index is implicit in the tree itself)
                            if (lastTreePosition == 0)
                            {
                                treeRoots[activePrefix] = (ushort)(codeStart + codeCount);
                            }
                            else if (activeSuffix < foundSuffix)
                            {
                                codeTable[lastTreePosition - codeStart].LowerIndex = (uint)(codeStart + codeCount);
                            }
                            else // activeSuffix > foundSuffix
                            {
                                codeTable[lastTreePosition - codeStart].HigherIndex = (uint)(codeStart + codeCount);
                            }

                            // If the new code is past the maximum representable at this code width, increase the code width
                            if ((codeStart + codeCount) == (1 << codeBitsUsed))
                            {
                                codeBitsUsed++; // NOTE: GIF does *not* do an early change of the code-width
                            }
                            // Fill table entry:
                            codeTable[codeCount++] = new LZWTableEntry(activeSuffix);
                        }

                        // Active prefix has been written, start searching from the suffix
                        activePrefix = activeSuffix;

                        nextIndex :;
                    }

                    WriteVariableBitsChunked(activePrefix, codeBitsUsed, ref pending);
                    WriteVariableBitsChunked(endCode, codeBitsUsed, ref pending);

                    FinishVariableBitsChunked(ref pending);
                    FinishChunked(ref pending);
                }
            }

            // GIF: Trailer
            WriteByte(0x3B);


            // Finished creating gif data at this point, write it out
            stream.Write(buffer, 0, bufferPosition);
            bufferPosition = 0;
        }